zombiebox-platform-samsung
Version:
Samsung Orsay platfrom adapter for ZombieBox Smart TV framework
682 lines (573 loc) • 14.1 kB
JavaScript
/*
* This file is part of the ZombieBox package.
*
* Copyright © 2013-2019, Interfaced
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import {State} from 'zb/device/interfaces/i-video';
import AbstractVideo from 'zb/device/abstract-video';
import Rect from 'zb/geometry/rect';
import Audio from './audio';
import ScreenSaverController from './screen-saver-controller';
import ViewPort from './view-port';
import VolumeOSDController from './volume-osd-controller';
/**
* Video object abstraction
*/
export default class Video extends AbstractVideo {
/**
* @param {Rect} rect
* @param {ScreenSaverController} screenSaverController
* @param {VolumeOSDController} volumeOSDController
* @param {string} hardwareVersion
* @param {PluginPlayer} pluginPlayer
* @param {PluginAudio} pluginAudio
* @param {PluginTV} pluginTV
*/
constructor(
rect,
screenSaverController,
volumeOSDController,
hardwareVersion,
pluginPlayer,
pluginAudio,
pluginTV
) {
super(rect);
/**
* @type {ViewPort}
* @protected
*/
this._viewport;
/**
* @type {ScreenSaverController}
* @protected
*/
this._screenSaverController = screenSaverController;
/**
* @type {VolumeOSDController}
* @protected
*/
this._volumeOSDController = volumeOSDController;
/**
* @type {PluginPlayer}
* @protected
*/
this._pluginPlayer = pluginPlayer;
/**
* @type {PluginAudio}
* @protected
*/
this._pluginAudio = pluginAudio;
/**
* @type {PluginTV}
* @protected
*/
this._pluginTV = pluginTV;
/**
* @type {Audio}
* @protected
*/
this._audio = new Audio(this._pluginAudio, this._pluginTV);
/**
* @type {?State}
* @protected
*/
this._stateBeforeBuffering = null;
/**
* @type {number}
* @protected
*/
this._position = NaN;
/**
* @type {number}
* @protected
*/
this._rate = NaN;
/**
* @type {number}
* @protected
*/
this._startPosition = NaN;
/**
* @type {Array<Function>}
* @protected
*/
this._globalMethods = [];
/**
* @type {boolean}
* @protected
*/
this._isInfoReady = false;
/**
* @type {boolean}
* @protected
*/
this._isFirstTimeUpdateFired = false;
/**
* @type {boolean}
* @protected
*/
this._isPlayFromPositionInProgress = false;
/**
* @type {boolean}
* @protected
*/
this._isPlayFromPositionBroken = ['2012', '2013'].indexOf(hardwareVersion) !== -1;
/**
* @type {?boolean}
* @protected
*/
this._wasVolumeOSDEnabledBeforePlayFromPosition = null;
/**
* @type {?boolean}
* @protected
*/
this._wasVolumeMutedBeforePlayFromPosition = null;
/**
* @type {string}
* @private
*/
this._url = '';
this._finishPlayFromPosition = this._finishPlayFromPosition.bind(this);
this._initViewPort();
this._setState(State.UNINITED);
this._reset();
}
/**
* @override
*/
setPlaybackRate(rate) {
this._pluginPlayer.SetPlaybackSpeed(rate);
this._setInternalPlaybackRate(rate);
}
/**
* @override
*/
getPlaybackRate() {
return this._rate;
}
/**
* @override
*/
resume() {
this._pluginPlayer.Resume();
this._setState(State.PLAYING);
this._fireEvent(this.EVENT_PLAY);
this.setPlaybackRate(1); // For reset playback rate after forward/rewind
this._screenSaverController.enableScreenSaver(false);
}
/**
* @override
*/
destroy() {
this._pluginPlayer.Stop();
this._setState(State.DEINITED);
this._url = '';
}
/**
* @override
*/
pause() {
this._pluginPlayer.Pause();
this._setState(State.PAUSED);
this._fireEvent(this.EVENT_PAUSE);
this._setInternalPlaybackRate(0);
this._screenSaverController.enableScreenSaver(true);
}
/**
* @override
*/
stop() {
this._pluginPlayer.Stop();
this._setState(State.STOPPED);
this._fireEvent(this.EVENT_STOP);
this._setInternalPlaybackRate(1);
this._screenSaverController.enableScreenSaver(true);
}
/**
* @override
*/
setVolume(value) {
const volume = this._audio.getVolume();
const isVolumeUp = volume < value;
let distance = Math.abs(volume - value);
while (distance > 0) {
if (isVolumeUp) {
this._audio.up();
} else {
this._audio.down();
}
distance--;
}
this._fireEvent(this.EVENT_VOLUME_CHANGE, value);
}
/**
* @override
*/
getVolume() {
return this._audio.getVolume();
}
/**
* @override
*/
getMuted() {
return this._audio.isMuted();
}
/**
* @override
*/
setMuted(value) {
if (value !== this.getMuted()) {
this._audio.setMuted(value);
this._fireEvent(this.EVENT_VOLUME_CHANGE, this.getVolume());
}
}
/**
* @override
*/
getDuration() {
// TODO: check for stream ready
return parseInt(this._pluginPlayer.GetDuration(), 10);
}
/**
* @override
*/
play(url, startFrom = NaN) {
let samsungUrl = url;
if (url.indexOf('m3u8') !== -1 && url.indexOf('|COMPONENT=HLS') === -1) {
samsungUrl += '|COMPONENT=HLS';
}
const stoppedStates = [
State.DEINITED,
State.UNINITED,
State.STOPPED
];
if (stoppedStates.indexOf(this.getState()) === -1) {
this.stop();
}
this._reset();
this._startPosition = startFrom;
this._bindPluginListeners();
this._pluginPlayer.Play(samsungUrl);
this._setState(State.LOADING);
this._url = url;
this._viewport.updateViewPort();
if (!isNaN(this._startPosition)) {
this._startPlayFromPosition();
this.once(this.EVENT_STOP, this._finishPlayFromPosition);
this.once(this.EVENT_LOADED_META_DATA, this._finishPlayFromPosition);
}
this._screenSaverController.enableScreenSaver(false);
}
/**
* @override
*/
getPosition() {
return this._position;
}
/**
* @override
*/
setPosition(position) {
const currentPosition = this.getPosition();
const diff = Math.round((currentPosition - position) / 1000);
if (diff < 0) {
this._pluginPlayer.JumpForward(Math.abs(diff));
} else if (diff > 0) {
this._pluginPlayer.JumpBackward(diff);
}
}
/**
* @override
*/
togglePause() {
if (this._state === State.PAUSED ||
this._state === State.SEEKING) {
this.resume();
} else {
this.pause();
}
}
/**
* @override
*/
forward() {
if (this._state === State.PLAYING ||
this._state === State.SEEKING) {
if (this._rate < 0) {
if (this.setPlaybackRate(2)) {
this._setState(State.SEEKING);
return true;
}
} else if (this._rate * 2 <= 32) {
if (this.setPlaybackRate(this._rate * 2)) {
this._setState(State.SEEKING);
return true;
}
}
}
return false;
}
/**
* @override
*/
rewind() {
if (this._state === State.PLAYING ||
this._state === State.SEEKING) {
if (this._rate > 0) {
if (this.setPlaybackRate(-2)) {
this._setState(State.SEEKING);
return true;
}
} else if (this._rate * 2 >= -32) {
if (this.setPlaybackRate(this._rate * 2)) {
this._setState(State.SEEKING);
return true;
}
}
}
return false;
}
/**
* @override
*/
getUrl() {
return this._url;
}
/**
* @override
*/
_createViewPort(containerRect) {
return new ViewPort(containerRect, this._pluginPlayer);
}
/**
* @param {number} rate
* @protected
*/
_setInternalPlaybackRate(rate) {
if (rate !== this._rate) {
this._rate = rate;
this._fireEvent(this.EVENT_RATE_CHANGE, this._rate);
}
}
/**
* @protected
*/
_bindPluginListeners() {
this._unbindAllListeners(this._createGlobalMethod(() => {/* Noop */}));
this._pluginPlayer.OnBufferingComplete = this._createGlobalMethod(this._onBufferingComplete);
this._pluginPlayer.OnBufferingStart = this._createGlobalMethod(this._onBufferingStart);
this._pluginPlayer.OnConnectionFailed = this._createGlobalMethod(this._onConnectionFailed);
this._pluginPlayer.OnNetworkDisconnected = this._createGlobalMethod(this._onNetworkDisconnected);
this._pluginPlayer.OnRenderError = this._createGlobalMethod(this._onRenderError);
this._pluginPlayer.OnRenderingComplete = this._createGlobalMethod(this._onRenderingComplete);
this._pluginPlayer.OnStreamInfoReady = this._createGlobalMethod(this._onStreamInfoReady);
this._pluginPlayer.OnStreamNotFound = this._createGlobalMethod(this._onStreamNotFound);
this._pluginPlayer.OnAuthenticationFailed = this._createGlobalMethod(this._onAuthFailed);
this._pluginPlayer.OnCurrentPlayTime = this._createGlobalMethod(this._onCurrentPlayTime);
this._pluginPlayer.OnResolutionChanged = this._createGlobalMethod(this._onResolutionChanged);
}
/**
* @param {string} listener
* @protected
*/
_unbindAllListeners(listener) {
this._pluginPlayer.OnBufferingComplete = listener;
this._pluginPlayer.OnBufferingStart = listener;
this._pluginPlayer.OnConnectionFailed = listener;
this._pluginPlayer.OnNetworkDisconnected = listener;
this._pluginPlayer.OnRenderError = listener;
this._pluginPlayer.OnRenderingComplete = listener;
this._pluginPlayer.OnStreamInfoReady = listener;
this._pluginPlayer.OnStreamNotFound = listener;
this._pluginPlayer.OnAuthenticationFailed = listener;
this._pluginPlayer.OnCurrentPlayTime = listener;
this._pluginPlayer.OnResolutionChanged = listener;
}
/**
* @protected
*/
_onBufferingComplete() {
if (this._stateBeforeBuffering) {
this._setState(this._stateBeforeBuffering);
if (this.getState() === State.PLAYING && isNaN(this._startPosition)) {
this._fireEvent(this.EVENT_PLAY);
}
this._stateBeforeBuffering = null;
}
}
/**
* @protected
*/
_onBufferingStart() {
this._stateBeforeBuffering = this.getState();
this._setState(State.BUFFERING);
this._fireEvent(this.EVENT_BUFFERING);
}
/**
* @protected
*/
_onConnectionFailed() {
this._setState(State.ERROR);
this._fireEvent(this.EVENT_ERROR, 'connection-failed');
}
/**
* @protected
*/
_onNetworkDisconnected() {
this._setState(State.ERROR);
this._fireEvent(this.EVENT_ERROR, 'network-disconnected');
}
/**
* @protected
*/
_onRenderError() {
this._setState(State.ERROR);
this._fireEvent(this.EVENT_ERROR, 'render-error');
}
/**
* @protected
*/
_onStreamNotFound() {
this._setState(State.ERROR);
this._fireEvent(this.EVENT_ERROR, 'stream-not-found');
}
/**
* @protected
*/
_onAuthFailed() {
this._setState(State.ERROR);
this._fireEvent(this.EVENT_ERROR, 'auth-failed');
}
/**
* @param {string} position
* @protected
*/
_onCurrentPlayTime(position) {
// Prevent foreign time updates
if (!this._isInfoReady) {
return;
}
const parsedPosition = parseInt(position, 10);
if (this._isPlayFromPositionInProgress && !isNaN(this._startPosition)) {
let isTimeToSetPosition = true;
if (this._isPlayFromPositionBroken) {
isTimeToSetPosition = parsedPosition >= PLAY_FROM_POSITION_DELAY;
}
if (isTimeToSetPosition) {
this._pluginPlayer.JumpForward(Math.round(this._startPosition / 1000));
this._startPosition = NaN;
}
return;
}
this._position = parsedPosition;
if (!this._isFirstTimeUpdateFired && this._position > 0) {
this._isFirstTimeUpdateFired = true;
this._fireEvent(this.EVENT_LOADED_META_DATA);
}
if (this.getPlaybackRate() === 1 && this.getState() !== State.PLAYING) {
this._setState(State.PLAYING);
this._fireEvent(this.EVENT_PLAY);
}
this._fireEvent(this.EVENT_TIME_UPDATE, this._position);
}
/**
* @protected
*/
_onRenderingComplete() {
this._fireEvent(this.EVENT_ENDED);
}
/**
* @protected
*/
_onStreamInfoReady() {
this._isInfoReady = true;
this._position = this._startPosition || 0;
this._fireEvent(this.EVENT_DURATION_CHANGE, this.getDuration());
this._viewport.updateVideoInfo();
}
/**
* @protected
*/
_onResolutionChanged() {
this._viewport.updateVideoInfo();
}
/**
* @protected
*/
_reset() {
this._pluginPlayer.SetPlaybackSpeed(1);
this._rate = 1;
this._position = 0;
this._isInfoReady = false;
this._isFirstTimeUpdateFired = false;
this._viewport.resetVideoInfo();
}
/**
* @param {Function} method
* @return {string}
* @protected
*/
_createGlobalMethod(method) {
const index = this._globalMethods.indexOf(method);
let methodName = `SamsungPlayerCallback${index}`;
if (index === -1) {
this._globalMethods.push(method);
methodName = `SamsungPlayerCallback${this._globalMethods.length - 1}`;
window[methodName] = method.bind(this);
}
return methodName;
}
/**
* Playing from a position may cause first frame displaying before seeking,
* so we have to hide the display area and disable sound until the position is applied
* @protected
*/
_startPlayFromPosition() {
this._isPlayFromPositionInProgress = true;
this._wasVolumeMutedBeforePlayFromPosition = this.isMuted();
this._wasVolumeOSDEnabledBeforePlayFromPosition = this._volumeOSDController.isVolumeOSDEnabled();
this._audio.setMuted(true);
this._volumeOSDController.enableVolumeOSD(false);
this._minimizeDisplayArea(true);
}
/**
* @see _startPlayFromPosition
* @protected
*/
_finishPlayFromPosition() {
if (!this._isPlayFromPositionInProgress) {
return;
}
this._audio.setMuted(!!this._wasVolumeMutedBeforePlayFromPosition);
if (this._wasVolumeOSDEnabledBeforePlayFromPosition) {
this._volumeOSDController.enableVolumeOSD(true);
}
this._minimizeDisplayArea(false);
this._isPlayFromPositionInProgress = false;
this._wasVolumeMutedBeforePlayFromPosition = null;
this._wasVolumeOSDEnabledBeforePlayFromPosition = null;
}
/**
* @param {boolean} state
* @protected
*/
_minimizeDisplayArea(state) {
if (state) {
// Change display area directly, without applying of display ratio
this._pluginPlayer.SetDisplayArea(0, 0, 1, 1);
} else {
this._viewport.updateViewPort();
}
}
}
/**
* This is the magic heuristic constant for 2012, 2013 model year devices
* When playing started from a specified position we have to use this delay before seeking,
* otherwise the position will not be applied
* @const {number}
*/
export const PLAY_FROM_POSITION_DELAY = 100;