/**
* The Seeker can be used to fast-forward or rewind a media-player.
*
* @example
* var seeker = Seeker();
*
* seeker.attach(function onSpeedChanged (newSpeed) {
* // do something with the newSpeed, e.g. update the fast-forward or rewind player controls button
* }, function onCurrentTimeUpdated (currentTime) {
* // do something with the currentTime, e.g. update the scrub bar
* }, function onSeek (position) {
* // do something with the position, e.g. communicate it to the TimeLineManager
* });
*
* // When the fast-forward player controls button is selected
* seeker.fastForward();
*
* // When the rewind player controls button is selected
* seeker.rewind():
*
* // When the play player controls button is selected (while seeking)
* seeker.confirm();
*
* // When the seek action should be cancelled/reverted
* seeker.cancel();
*
* // When the seeker is no longer used
* seeker.detach();
*/
define('application/widgets/player/liveseeker', [
'rofl/lib/utils',
'antie/runtimecontext'
], function (
Utils,
RuntimeContext
) {
'use strict';
var application = RuntimeContext.getCurrentApplication(),
config = application.getConfiguration().seeker || {},
device = RuntimeContext.getDevice(),
mediaPlayer = device.getMediaPlayer(),
instance,
/**
* Interval value in milliseconds.
*
* @type {number} Milliseconds.
*/
INTERVAL = config.interval || 500,
/**
* Speed divisor used for calculating the effective currentTime mutation.
*
* @type {number}
*/
DIVISOR = INTERVAL / 1000,
/**
* Pre-defined speed steps.
*
* @type {string[]} Step.
*/
STEPS = config.steps
|| ['2',
'4',
'8',
'16',
'32'],
/**
* The Seeker can be used to fast-forward or rewind a media-player.
*
* @name product-layer.player.widgets.Seeker
* @class
*/
Seeker = function () {
this._playerStatus = null;
this._onSpeedChanged = null;
this._onCurrentTimeUpdated = null;
this._steps = STEPS;
this._interval = null;
this._currentTime = null;
this._speed = null;
this._seekSteps = 0;
};
Seeker.prototype = {
/**
* Returns the value of a speed step that is to be added to the currentTime.
*
* @returns {number} Top be applied time mutation.
* @private
*/
_getSpeedStep: function _getSpeedStepFn () {
var steps = this._steps,
speed = steps[Math.abs(this._speed) - 1],
multiplier = this._speed < 0 ? -1 : 1;
return speed * multiplier;
},
/**
* Handles speed changes.
*
* @private
*/
_onSpeedChange: function _onSpeedChangeFn () {
var onChangeVal = this._speed
? this._getSpeedStep()
: null;
if (this._onSpeedChanged) {
this._onSpeedChanged(onChangeVal);
} else {
throw 'No _onSpeedChanged method defined.';
}
},
/**
* Handles current time changes.
*
* @private
*/
_onCurrentTimeChange: function __onCurrentTimeChangeFn () {
if (this._onCurrentTimeUpdated) {
this._onCurrentTimeUpdated({
relativePlaybackTime: this._relativePlaybackTime,
currentTime: this._currentTime
});
} else {
throw 'No _onCurrentTimeUpdated method defined.';
}
},
/**
* Stops the running interval.
*
* @private
*/
_stopInterval: function _stopIntervalFn () {
if (this._interval) {
clearInterval(this._interval);
this._interval = null;
}
},
/**
* Stops the current seeking action.
*
* @private
*/
_onStopSeeking: function _onStopSeekingFn () {
this._stopInterval();
this._speed = null;
this._onSpeedChange();
this._onCurrentTimeChange();
},
/**
* Handles interval callbacks.
*
* @private
*/
_onInterval: function _onIntervalFn () {
var step;
if (this._speed) {
step = Math.round(DIVISOR * this._getSpeedStep()) * 1000;
this._relativePlaybackTime += step;
this._currentTime += step;
this._seekSteps += step;
this._onCurrentTimeChange();
}
},
/**
* Starts the seeking interval.
*
* @private
*/
_startInterval: function _startIntervalFn () {
this._interval = setInterval(Utils.bind(this._onInterval, this)
, INTERVAL);
},
/**
* Gets triggered when the seeking starts.
*
* @private
*/
_onStartSeeking: function _onStartSeekingFn () {
this._oldPlaybackTime = this._relativePlaybackTime;
this._oldCurrentTime = this._currentTime;
mediaPlayer.pause();
this._startInterval();
},
/**
* Gets triggered right after seeking speed changes.
*
* @private
*/
_onAfterSpeedMutation: function _onAfterSpeedMutationFn () {
if (Math.abs(this._speed) <= this._steps.length) {
this._onSpeedChange();
} else { // Highest step passed, back to neutral state.
this.confirm();
}
},
/**
* Gets triggered right before seeking speed changes.
*
* @param {int} direction - Seeking direction.
* @private
*/
_onBeforeSpeedMutation: function _onBeforeSpeedMutationFn (direction) {
// Handle change of seeking direction
if ((direction < 0 && this._speed > 0)
|| (direction > 0 && this._speed < 0)) {
this._onStopSeeking();
}
// Set speed to neutral position
if (this._speed === null) {
this._speed = 0;
this._onStartSeeking();
}
},
/**
* Sets the speed steps.
*
* @param {Array} steps - Speed steps.
*/
setSpeedSteps: function setSpeedStepsFn (steps) {
this._steps = steps || STEPS;
},
/**
* Cancels the current seeking action.
*/
cancel: function cancelFn () {
this._relativePlaybackTime = this._oldPlaybackTime;
this._currentTime = this._oldCurrentTime;
this._onStopSeeking();
mediaPlayer.resume();
},
/**
* Confirms the current seeking position as resume point.
*
* @param {number} [time] - The time to seek to. Optional.
*/
confirm: function confirmFn (time) {
var difference,
seekTo;
if (time && time !== this._currentTime) {
time = Math.round(time);
difference = this._currentTime - time;
this._seekSteps = -difference;
this._relativePlaybackTime = time - this._startTime;
this._currentTime = time;
}
seekTo = mediaPlayer.getCurrentTime() + (this._seekSteps / 1000);
this._stopInterval();
this._speed = null;
this._onSeek({
relativePlaybackTime: this._relativePlaybackTime,
currentTime: this._currentTime,
seekTo: seekTo
});
this._seekSteps = 0;
},
/**
* Increase rewind seeking speed.
*/
rewind: function rewindFn () {
if (!this.canSeek()) {
return;
}
this._onBeforeSpeedMutation(-1);
this._speed--;
this._onAfterSpeedMutation();
},
/**
* Increase fast forward seeking speed.
*/
fastForward: function fastForwardFn () {
if (!this.canSeek()) {
return;
}
this._onBeforeSpeedMutation(1);
this._speed++;
this._onAfterSpeedMutation();
},
/**
* Whether seeking is active or not.
*
* @returns {boolean} Active.
*/
isActive: function isActiveFn () {
return !!this._interval;
},
/**
* Returns whether seeking is allowed or not.
*
* @returns {boolean} Can seek.
*/
canSeek: function canSeekFn () {
return !!mediaPlayer.getSeekableRange();
},
/**
* Detaches the seeker from the media player.
*/
detach: function detachFn () {
this._stopInterval();
this._onSpeedChanged = null;
this._onCurrentTimeUpdated = null;
this._steps = null;
this._interval = null;
this._currentTime = null;
this._duration = null;
this._speed = null;
},
/**
* Sets the current program time.
*
* @param {number} startTime - The start time of the program.
* @param {number} endTime - The end time of the program.
* @param {number} currentPlaybackTime - The current playback time.
*/
setProgramTime: function (startTime, endTime, currentPlaybackTime) {
this._startTime = startTime;
this._endTime = endTime;
this._currentTime = currentPlaybackTime;
this._relativePlaybackTime = currentPlaybackTime - startTime;
},
/**
* Returns the seek direction.
*
* @returns {number} - Returns -1|1. 1 if the direction is forward, -1 if the direction is backwards.
*/
getDirection: function () {
return this._speed > 0 ? 1 : -1;
},
/**
* Sets the current time.
*
* @param {number} currentTime - The current time.
*/
setCurrentTime: function (currentTime) {
this._currentTime = currentTime;
this._relativePlaybackTime = currentTime - this._startTime;
},
/**
* Attaches the seeker on the media player.
*
* @param {Object} opts - The attach options.
* @param {Function} opts.onSpeedChanged - Handles on speed changed events.
* @param {Function} opts.onCurrentTimeUpdated - Handles on current time updated events.
* @param {Function} opts.onSeek - Gets triggered on confirming the seek action.
* @param {Array} [opts.steps] - Steps used for the different speeds.
*/
attach: function attachFn (opts) {
opts = opts || {};
this._onSpeedChanged = opts.onSpeedChanged;
this._onCurrentTimeUpdated = opts.onCurrentTimeUpdated;
this._onSeek = opts.onSeek;
this.setSpeedSteps(opts.steps);
}
};
return function () {
if (!instance) {
instance = new Seeker();
}
return instance;
};
});