import { EventDispatcher } from '../events/EventDispatcher'; import { Event } from '../events/Event'; import { IOErrorEvent } from '../events/IOErrorEvent'; import { URLRequest } from '../net/URLRequest'; import { SoundLoaderContext } from './SoundLoaderContext'; import { SoundChannel } from './SoundChannel'; import { SoundTransform } from './SoundTransform'; import { ByteArray } from '../utils/ByteArray'; import { ID3Info } from './ID3Info'; import { AssetLibrary, LoaderContext, WaveAudio, WaveAudioParser, URLRequest as URLRequestAway, AssetEvent, LoaderEvent, URLLoaderEvent, IAsset, IAudioChannel } from '@awayjs/core'; import { SecurityDomain } from '../SecurityDomain'; /** * Dispatched when data is received as a load operation progresses. * @eventType flash.events.ProgressEvent.PROGRESS [Event(name="progress", type="flash.events.Progress [EventEvent")] * Dispatched when a load operation starts. * @eventType flash.events.Event.OPEN [Event(name="open", type="flash.events. [EventEvent")] * Dispatched when an input/output error occurs that causes a load operation to fail. * @eventType flash.events.IOErrorEvent.IO_ERROR [Event(name="ioError", type="flash.events.IOError [EventEvent")] * Dispatched by a Sound object when ID3 data is available for an MP3 sound. * @eventType flash.events.Event.ID3 [Event(name="id3", type="flash.events. [EventEvent")] * Dispatched when data has loaded successfully. * @eventType flash.events.Event.COMPLETE [Event(name="complete", type="flash.events. [EventEvent")] * Dispatched when the runtime requests new audio data. * @eventType flash.events.SampleDataEvent.SAMPLE_DATA [Event(name="sampleData", type="flash.events.SampleData [EventEvent")] * The Sound class lets you work with sound in an application. The Sound class * lets you create a Sound object, load and play an external MP3 file into that object, * close the sound stream, and access * data about the sound, such as information about the number of bytes in the stream and * ID3 metadata. More detailed control of the sound is performed through the sound source — * the SoundChannel or Microphone object for the sound — and through the properties * in the SoundTransform class that control the output of the sound to the computer's speakers. * *

In Flash Player 10 and later and AIR 1.5 and later, you can also use this * class to work with sound that is generated dynamically. * In this case, the Sound object uses the function you assign to a * sampleData event handler to * poll for sound data. The sound is played as it is retrieved from a ByteArray object that * you populate with sound data. You can use * Sound.extract() to extract sound data from a * Sound object, * after which you can manipulate it before writing it back to the stream for playback. *

To control sounds that are embedded in a SWF file, * use the properties in the SoundMixer class.

* Note: The ActionScript 3.0 Sound API differs from ActionScript 2.0. * In ActionScript 3.0, you cannot take sound objects and arrange them in a hierarchy * to control their properties.

* When you use this class, consider the following security model: *

*

However, in Adobe AIR, content in the * application security sandbox (content * installed with the AIR application) are not restricted by these security limitations.

*

For more information related to security, see the Flash Player Developer Center Topic: * Security.

*/ export class Sound extends EventDispatcher { private _adaptee: WaveAudio; private _url: string; private _loading: boolean; private _playAfterLoad: boolean; private _pendingPlayCommand: IPendingPlayCommand; private _bytesLoaded: number; /** * Creates a new Sound object. If you pass a valid URLRequest object to the * Sound constructor, the constructor automatically calls the load() function * for the Sound object. * If you do not pass a valid URLRequest object to the Sound constructor, * you must call the load() function for the Sound object yourself, * or the stream will not load. * * Once load() is called on a Sound object, you can't later load * a different sound file into that Sound object. To load a different sound file, * create a new Sound object. * * In Flash Player 10 and later and AIR 1.5 and later, instead of using load(), * you can use the sampleData event handler to load sound dynamically into the Sound object. * @param stream The URL that points to an external MP3 file. * @param context An optional SoundLoader context object, which can define the buffer time * (the minimum number of milliseconds of MP3 data to hold in the Sound object's * buffer) and can specify whether the application should check for a cross-domain * policy file prior to loading the sound. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 * @refpath */ constructor (stream: URLRequest = null, context: SoundLoaderContext = null) { super(); this._bytesLoaded = 0; this._onAssetCompleteDelegate = (event: AssetEvent) => this.onAssetComplete(event); this._onLoaderCompleteDelegate = (event: LoaderEvent) => this.onLoaderComplete(event); this._onLoadErrorDelegate = (event: URLLoaderEvent) => this.onLoadError(event); if (stream) { this.loadWaveAudio(stream); } } private loadWaveAudio(stream: URLRequest) { this._url = stream.url; this._loading = true; AssetLibrary.removeEventListener(AssetEvent.ASSET_COMPLETE, this._onAssetCompleteDelegate); AssetLibrary.removeEventListener(LoaderEvent.LOADER_COMPLETE, this._onLoaderCompleteDelegate); AssetLibrary.removeEventListener(URLLoaderEvent.LOAD_ERROR, this._onLoadErrorDelegate); AssetLibrary.addEventListener(AssetEvent.ASSET_COMPLETE, this._onAssetCompleteDelegate); AssetLibrary.addEventListener(LoaderEvent.LOADER_COMPLETE, this._onLoaderCompleteDelegate); AssetLibrary.addEventListener(URLLoaderEvent.LOAD_ERROR, this._onLoadErrorDelegate); AssetLibrary.load(new URLRequestAway(this._url), new LoaderContext(), this._url, new WaveAudioParser()); } private _onAssetCompleteDelegate: (event: AssetEvent) => void; private onAssetComplete(event: AssetEvent): void { const asset: IAsset = event.asset; if (asset.isAsset(WaveAudio)) { this._adaptee = asset; this._loading = false; if (this._pendingPlayCommand) { this.play(this._pendingPlayCommand.startTime / 1000, this._pendingPlayCommand.loops, this._pendingPlayCommand.sndTransform); this._playAfterLoad = false; } this._pendingPlayCommand = null; } } private _onLoaderCompleteDelegate: (event: LoaderEvent) => void; private onLoaderComplete(event: LoaderEvent): void { if (event.url != this._url) { return; } this._loading = false; this._bytesLoaded = 1000; AssetLibrary.removeEventListener(AssetEvent.ASSET_COMPLETE, this._onAssetCompleteDelegate); AssetLibrary.removeEventListener(LoaderEvent.LOADER_COMPLETE, this._onLoaderCompleteDelegate); AssetLibrary.removeEventListener(URLLoaderEvent.LOAD_ERROR, this._onLoadErrorDelegate); this.dispatchEvent(new ( this.sec).flash.events.Event(Event.COMPLETE)); } private _onLoadErrorDelegate: (event: URLLoaderEvent) => void; private onLoadError(event: URLLoaderEvent): void { AssetLibrary.removeEventListener(AssetEvent.ASSET_COMPLETE, this._onAssetCompleteDelegate); AssetLibrary.removeEventListener(LoaderEvent.LOADER_COMPLETE, this._onLoaderCompleteDelegate); AssetLibrary.removeEventListener(URLLoaderEvent.LOAD_ERROR, this._onLoadErrorDelegate); console.log('load error in Sound', event); this._loading = false; this.dispatchEvent(new ( this.sec).flash.events.IOErrorEvent(IOErrorEvent.ERROR)); } public get adaptee(): WaveAudio { return ( this._adaptee); } public set adaptee(adaptee: WaveAudio) { this._adaptee = adaptee; } /** * Returns the currently available number of bytes in this sound object. This property is * usually useful only for externally loaded files. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 * @refpath */ public get bytesLoaded (): number { return this._bytesLoaded; } /** * Returns the total number of bytes in this sound object. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 * @refpath */ public get bytesTotal (): number { return 1000; } /** * Provides access to the metadata that is part of an MP3 file. * * MP3 sound files can contain ID3 tags, which provide metadata about the * file. If an MP3 sound that you load using the Sound.load() * method contains ID3 tags, you can query these properties. Only ID3 tags * that use the UTF-8 character set are supported.Flash Player 9 and later and AIR support * ID3 2.0 tags, * specifically 2.3 and 2.4. The following tables list the standard ID3 2.0 tags * and the type of content the tags represent. The Sound.id3 property provides * access to these tags through the format * my_sound.id3.COMM, my_sound.id3.TIME, and so on. The first * table describes tags that can be accessed either through the ID3 2.0 property name or * the ActionScript property name. The second table describes ID3 tags that are supported but do not have * predefined properties in ActionScript. ID3 2.0 tagCorresponding * The following table describes ID3 tags that are supported but do not have * predefined properties in the Sound class. You access them by calling * mySound.id3.TFLT, mySound.id3.TIME, and so on. NOTE: None of * these tags are supported in Flash Lite 4. * that are in the same security sandbox as the sound file. For files in other sandboxes, there * are security checks.When you load the sound, using the load() method of the Sound class, you can * specify a context parameter, which is a SoundLoaderContext object. If you set the * checkPolicyFile property of the SoundLoaderContext object to true, Flash Player * checks for a URL policy file on the server from which the sound is loaded. If a * policy file exists and permits access from the domain of the loading SWF file, then the file is allowed * to access the id3 property of the Sound object; otherwise it is not.However, * in Adobe AIR, content in the application security sandbox (content * installed with the AIR application) are not restricted by these security limitations. * For more information related to security, see the Flash Player Developer Center Topic: * Security. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 */ public get id3 (): ID3Info { console.log('id3 not implemented yet in flash/Sound'); return null; } /** * Returns the buffering state of external MP3 files. If the value is true, * any playback is * currently suspended while the object waits for more data. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 * @refpath */ public get isBuffering (): boolean { console.log('isBuffering not implemented yet in flash/Sound'); return false; } /** * Indicates if the Sound.url property has been * truncated. When the isURLInaccessible value is true the * Sound.url value is only the domain of the final URL from which the sound loaded. * For example, the property is truncated if the sound is loaded from http://www.adobe.com/assets/hello.mp3, * and the Sound.url property has the value http://www.adobe.com. * The isURLInaccessible value is true only when all of the following are also true: * * An HTTP redirect occurred while loading the sound file. * The SWF file calling Sound.load() is from a different domain than * the sound file's final URL.The SWF file calling Sound.load() does not have permission to access * the sound file. Permission is granted to access the sound file the same way permission is granted * for the Sound.id3 property: establish a policy file and use the SoundLoaderContext.checkPolicyFile * property.Note: The isURLInaccessible property was added for Flash Player 10.1 and AIR 2.0. * However, this property is made available to SWF files of all versions when the * Flash runtime supports it. So, using some authoring tools in "strict mode" causes a compilation error. * To work around the error use the indirect syntax mySound["isURLInaccessible"], * or disable strict mode. If you are using Flash Professional CS5 * or Flex SDK 4.1, you can use and compile this API for runtimes released * before Flash Player 10.1 and AIR 2.For application * content in AIR, the value of this property is always false. * @langversion 3.0 * @playerversion Flash 10.1 * @playerversion AIR 2 */ public get isURLInaccessible (): boolean { console.log('isURLInaccessible not implemented yet in flash/Sound'); return false; } /** * The length of the current sound in milliseconds. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 */ public get length (): number { if (!this._adaptee) return; return this._adaptee.duration * 1000; } /** * The URL from which this sound was loaded. This property is applicable only to Sound * objects that were loaded using the Sound.load() method. For * Sound objects that are associated with a sound asset from a SWF file's library, the * value of the url property is null. * * When you first call Sound.load(), the url property * initially has a value of null, because the final URL is not yet known. * The url property will have a non-null value as soon as an * open event is dispatched from the Sound object.The url property contains the final, * absolute URL from which a sound was * loaded. The value of url is usually the same as the value passed to the * stream parameter of Sound.load(). * However, if you passed a relative URL to Sound.load() * the value of the url property represents the absolute URL. * Additionally, if the original URL request is redirected by an HTTP server, the value * of the url property reflects the final URL from which the sound file was actually * downloaded. This reporting of an absolute, final URL is equivalent to the behavior of * LoaderInfo.url.In some cases, the value of the url property is truncated; see the * isURLInaccessible property for details. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 */ public get url (): string { return this._url; } /** * Closes the stream, causing any download of data to cease. * No data may be read from the stream after the close() * method is called. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 * @refpath * @throws IOError The stream could not be closed, or * the stream was not open. */ public close () { console.log('close not implemented yet in flash/Sound'); } /** * Extracts raw sound data from a Sound object. * * This method is designed to be used when you are working * with dynamically generated audio, using a function you assign * to the sampleData event for a different Sound object. * That is, you can use this method to extract sound data from a Sound object. * Then you can write the data to the byte array that another Sound object is using * to stream dynamic audio.The audio data is placed in the target byte * array starting from the current position of the byte array. * The audio data is always exposed as 44100 Hz Stereo. The sample type is a 32-bit floating-point value, * which can be converted to a Number using ByteArray.readFloat(). * @param target A ByteArray object in which the extracted sound samples are placed. * @param length The number of sound samples to extract. * A sample contains both the left and right channels — that is, two 32-bit floating-point values. * @param startPosition The sample at which extraction begins. * If you don't specify a value, the first call to Sound.extract() starts at the beginning * of the sound; subsequent calls without a value for startPosition * progress sequentially through the file. * @return The number of samples written to the ByteArray specified in the target parameter. * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @refpath */ public extract (target: ByteArray, length: number, startPosition: number = -1): number { console.log('extract not implemented yet in flash/Sound'); return 0; } /** * Initiates loading of an external MP3 file from the specified URL. If you provide * a valid URLRequest object to the Sound constructor, the constructor calls * Sound.load() for you. You only need to call Sound.load() * yourself if you * don't pass a valid URLRequest object to the Sound constructor or you pass a null * value. * * Once load() is called on a Sound object, you can't later load * a different sound file into that Sound object. To load a different sound file, * create a new Sound object.When using this method, consider the following security model:Calling Sound.load() * is not allowed if the calling file is in the * local-with-file-system sandbox and the sound is in a network sandbox. * Access from the local-trusted or local-with-networking sandbox requires permission * from a website through a URL policy file.You cannot connect to commonly reserved ports. * For a complete list of blocked ports, see "Restricting Networking APIs" in the * ActionScript 3.0 Developer's Guide.You can prevent a SWF file from using this method by setting the * allowNetworking parameter of the object and embed * tags in the HTML page that contains the SWF content. In Flash Player 10 and later, * if you use a multipart Content-Type (for example "multipart/form-data") * that contains an upload (indicated by a "filename" parameter * in a "content-disposition" header within the POST body), * the POST operation is subject to the security rules applied to uploads: * The POST operation must be performed in response to a user-initiated action * such as a mouse click or key press.If the POST operation is cross-domain * (the POST target is not on the same server as the SWF file * that is sending the POST request), * the target server must provide a URL policy file that permits cross-domain access. * Also, for any multipart Content-Type, the syntax must be valid (according to the RFC2046 standards). * If the syntax appears to be invalid, the POST operation is subject to the security rules applied to uploads. * In Adobe AIR, content in the application security sandbox (content * installed with the AIR application) are not restricted by these security limitations.For more * information related to security, see the Flash Player Developer Center Topic: * Security. * @param stream A URL that points to an external MP3 file. * @param context An optional SoundLoader context object, which can define the buffer time * (the minimum number of milliseconds of MP3 data to hold in the Sound object's * buffer) and can specify whether the application should check for a cross-domain * policy file prior to loading the sound. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 * @refpath * @throws IOError A network error caused the load to fail. * @throws SecurityError Local untrusted files may not communicate with * the Internet. You can work around this by reclassifying this file * as local-with-networking or trusted. * @throws SecurityError You cannot connect to commonly reserved ports. * For a complete list of blocked ports, see "Restricting Networking APIs" in the * ActionScript 3.0 Developer's Guide. * @throws IOError The digest property of the stream object is not * null. You should only set the digest property of a URLRequest object * when calling the URLLoader.load() method when loading a SWZ file (an Adobe * platform component). */ public load (stream: URLRequest, context: SoundLoaderContext = null) { this.loadWaveAudio(stream); } public loadCompressedDataFromByteArray (_bytes: ByteArray, _bytesLength: number) { console.log('loadCompressedDataFromByteArray not implemented yet in flash/Sound'); } public loadPCMFromByteArray ( _bytes: ByteArray, _samples: number, _format: string = 'float', _stereo: boolean = true, _sampleRate: number = 44100) { console.log('loadPCMFromByteArray not implemented yet in flash/Sound'); } /** * Generates a new SoundChannel object to play back the sound. This method * returns a SoundChannel object, which you access to stop the sound and to monitor volume. * (To control the volume, panning, and balance, access the SoundTransform object assigned * to the sound channel.) * @param startTime The initial position in milliseconds at which playback should * start. * @param loops Defines the number of times a sound loops back to the startTime value * before the sound channel stops playback. * @param sndTransform The initial SoundTransform object assigned to the sound channel. * @return A SoundChannel object, which you use to control the sound. * This method returns null if you have no sound card * or if you run out of available sound channels. The maximum number of * sound channels available at once is 32. * @langversion 3.0 * @playerversion Flash 9 * @playerversion Lite 4 * @refpath */ public play (startTime: number = 0, loops: number = 0, sndTransform: SoundTransform = null): SoundChannel { if (!this.adaptee && !this._loading) { console.warn('[Sound#play]: no adaptee exists!'); // return empty SoundChannel to prevent crash when adaptee is not exist return new ( this.sec).flash.media.SoundChannel(); } if (!this.adaptee && this._loading) { this._pendingPlayCommand = { startTime: startTime, loops: loops, sndTransform: sndTransform, sndChannel: new ( this.sec).flash.media.SoundChannel() }; return this._pendingPlayCommand.sndChannel; } startTime = isNaN(startTime) ? 0 : Math.floor(startTime); loops = isNaN(loops) ? 0 : loops < 1 ? 1 : Math.floor(loops); const channel: IAudioChannel = this._adaptee.play(startTime / 1000, loops); if (!channel) { console.debug('[Sound] Sound channel missed, channel pool overflow for:', this._adaptee.id); } const newSoundChannel: SoundChannel = this._pendingPlayCommand ? this._pendingPlayCommand.sndChannel : new ( this.sec).flash.media.SoundChannel(); sndTransform = sndTransform || new ( this.sec).flash.media.SoundTransform(); newSoundChannel.init( channel, sndTransform ); return newSoundChannel; } /* internal */ stopAll (): void { this._adaptee.stop(); } } interface IPendingPlayCommand { startTime: number, loops: number, sndTransform: SoundTransform, sndChannel: SoundChannel }