# sound/TrackPlayer.js

    /**
     * Track Player.
     *
     * @author Alain Pitiot
     * @version 2022.2.3
     * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
     * @license Distributed under the terms of the MIT License
     */

    import { SoundPlayer } from "./SoundPlayer.js";

    /**
     * <p>This class handles the playback of sound tracks.</p>
     *
     * @extends SoundPlayer
     * @todo stopTime is currently not implemented (tracks will play from startTime to finish)
     * @todo stereo is currently not implemented
     */
    export class TrackPlayer extends SoundPlayer
    {
    	/**
    	 * @memberOf module:sound
    	 * @param {Object} options
    	 * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
    	 * @param {Object} options.howl - the sound object (see {@link https://howlerjs.com/})
    	 * @param {number} [options.startTime= 0] - start of playback (in seconds)
    	 * @param {number} [options.stopTime= -1] - end of playback (in seconds)
    	 * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo
    	 * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0)
    	 * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played
    	 */
    	constructor({
    		psychoJS,
    		howl,
    		startTime = 0,
    		stopTime = -1,
    		stereo = true,
    		volume = 0,
    		loops = 0,
    	} = {})
    	{
    		super(psychoJS);

    		this._addAttribute("howl", howl);
    		this._addAttribute("startTime", startTime);
    		this._addAttribute("stopTime", stopTime);
    		this._addAttribute("stereo", stereo);
    		this._addAttribute("loops", loops);
    		this._addAttribute("volume", volume);

    		this._currentLoopIndex = -1;
    	}

    	/**
    	 * Determine whether this player can play the given sound.
    	 *
    	 * @param {module:sound.Sound} sound - the sound, which should be the name of an audio resource
    	 * 	file
    	 * @return {Object|undefined} an instance of TrackPlayer that can play the given track or undefined otherwise
    	 */
    	static accept(sound)
    	{
    		// if the sound's value is a string, we check whether it is the name of a resource:
    		if (typeof sound.value === "string")
    		{
    			const howl = sound.psychoJS.serverManager.getResource(sound.value);
    			if (typeof howl !== "undefined")
    			{
    				// build the player:
    				const player = new TrackPlayer({
    					psychoJS: sound.psychoJS,
    					howl: howl,
    					startTime: sound.startTime,
    					stopTime: sound.stopTime,
    					stereo: sound.stereo,
    					loops: sound.loops,
    					volume: sound.volume,
    				});
    				return player;
    			}
    		}

    		// TonePlayer is not an appropriate player for the given sound:
    		return undefined;
    	}

    	/**
    	 * Get the duration of the sound, in seconds.
    	 *
    	 * @return {number} the duration of the track, in seconds
    	 */
    	getDuration()
    	{
    		return this._howl.duration();
    	}

    	/**
    	 * Set the duration of the track.
    	 *
    	 * @param {number} duration_s - the duration of the track in seconds
    	 */
    	setDuration(duration_s)
    	{
    		if (typeof this._howl !== "undefined")
    		{
    			// Unfortunately Howler.js provides duration setting method
    			this._howl._duration = duration_s;
    		}
    	}

    	/**
    	 * Set the volume of the tone.
    	 *
    	 * @param {Integer} volume - the volume of the track (must be between 0 and 1.0)
    	 * @param {boolean} [mute= false] - whether or not to mute the track
    	 */
    	setVolume(volume, mute = false)
    	{
    		this._volume = volume;

    		this._howl.volume(volume);
    		this._howl.mute(mute);
    	}

    	/**
    	 * Set the number of loops.
    	 *
    	 * @param {number} loops - how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.
    	 */
    	setLoops(loops)
    	{
    		this._loops = loops;
    		this._currentLoopIndex = -1;

    		if (loops === 0)
    		{
    			this._howl.loop(false);
    		}
    		else
    		{
    			this._howl.loop(true);
    		}
    	}

    	/**
    	 * Start playing the sound.
    	 *
    	 * @param {number} loops - how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.
    	 * @param {number} [fadeDuration = 17] - how long should the fading in last in ms
    	 */
    	play(loops, fadeDuration = 17)
    	{
    		if (typeof loops !== "undefined")
    		{
    			this.setLoops(loops);
    		}

    		// handle repeats:
    		if (loops > 0)
    		{
    			const self = this;
    			this._howl.on("end", (event) =>
    			{
    				++this._currentLoopIndex;
    				if (self._currentLoopIndex > self._loops)
    				{
    					self.stop();
    				}
    				else
    				{
    					self._howl.seek(self._startTime);
    					self._id = self._howl.play();
    					self._howl.fade(0, self._volume, fadeDuration, self._id);
    				}
    			});
    		}

    		this._howl.seek(this._startTime);
    		this._id = this._howl.play();
    		this._howl.fade(0, this._volume, fadeDuration, this._id);
    	}

    	/**
    	 * Stop playing the sound immediately.
    	 *
    	 * @param {number} [fadeDuration = 17] - how long should the fading out last in ms
    	 */
    	stop(fadeDuration = 17)
    	{
    		this._howl.once("fade", (id) =>
    		{
    			this._howl.stop(id);
    			this._howl.off("end");
    		});
    		this._howl.fade(this._howl.volume(), 0, fadeDuration, this._id);
    	}
    }