playbackstrategy_modifiers_samsungmaple.js
import MediaPlayerBase from "../modifiers/mediaplayerbase"
import DebugTool from "../../debugger/debugtool"
function SamsungMaple() {
const playerPlugin = document.getElementById("playerPlugin")
let state = MediaPlayerBase.STATE.EMPTY
let deferSeekingTo = null
let postBufferingState = null
let tryingToPause = false
let currentTimeKnown = false
let mediaType
let source
let mimeType
let range
let currentTime
let eventCallbacks = []
let eventCallback
function initialiseMedia(type, url, mediaMimeType) {
if (getState() === MediaPlayerBase.STATE.EMPTY) {
mediaType = type
source = url
mimeType = mediaMimeType
_registerEventHandlers()
_toStopped()
} else {
_toError("Cannot set source unless in the '" + MediaPlayerBase.STATE.EMPTY + "' state")
}
}
function resume() {
postBufferingState = MediaPlayerBase.STATE.PLAYING
switch (getState()) {
case MediaPlayerBase.STATE.PLAYING:
break
case MediaPlayerBase.STATE.BUFFERING:
if (tryingToPause) {
tryingToPause = false
toPlaying()
}
break
case MediaPlayerBase.STATE.PAUSED:
playerPlugin.Resume()
toPlaying()
break
default:
_toError("Cannot resume while in the '" + getState() + "' state")
break
}
}
function playFrom(seconds) {
postBufferingState = MediaPlayerBase.STATE.PLAYING
const seekingTo = range ? _getClampedTimeForPlayFrom(seconds) : seconds
switch (getState()) {
case MediaPlayerBase.STATE.BUFFERING:
deferSeekingTo = seekingTo
break
case MediaPlayerBase.STATE.PLAYING:
_toBuffering()
if (!currentTimeKnown) {
deferSeekingTo = seekingTo
} else if (_isNearToCurrentTime(seekingTo)) {
toPlaying()
} else {
_seekToWithFailureStateTransition(seekingTo)
}
break
case MediaPlayerBase.STATE.PAUSED:
_toBuffering()
if (!currentTimeKnown) {
deferSeekingTo = seekingTo
} else if (_isNearToCurrentTime(seekingTo)) {
playerPlugin.Resume()
toPlaying()
} else {
_seekToWithFailureStateTransition(seekingTo)
playerPlugin.Resume()
}
break
case MediaPlayerBase.STATE.COMPLETE:
playerPlugin.Stop()
_setDisplayFullScreenForVideo()
playerPlugin.ResumePlay(_wrappedSource(), seekingTo)
_toBuffering()
break
default:
_toError("Cannot playFrom while in the '" + getState() + "' state")
break
}
}
function beginPlayback() {
postBufferingState = MediaPlayerBase.STATE.PLAYING
switch (getState()) {
case MediaPlayerBase.STATE.STOPPED:
_toBuffering()
_setDisplayFullScreenForVideo()
playerPlugin.Play(_wrappedSource())
break
default:
_toError("Cannot beginPlayback while in the '" + getState() + "' state")
break
}
}
function beginPlaybackFrom(seconds) {
postBufferingState = MediaPlayerBase.STATE.PLAYING
const seekingTo = range ? _getClampedTimeForPlayFrom(seconds) : seconds
switch (getState()) {
case MediaPlayerBase.STATE.STOPPED:
_setDisplayFullScreenForVideo()
playerPlugin.ResumePlay(_wrappedSource(), seekingTo)
_toBuffering()
break
default:
_toError("Cannot beginPlayback while in the '" + getState() + "' state")
break
}
}
function pause() {
postBufferingState = MediaPlayerBase.STATE.PAUSED
switch (getState()) {
case MediaPlayerBase.STATE.BUFFERING:
case MediaPlayerBase.STATE.PAUSED:
break
case MediaPlayerBase.STATE.PLAYING:
_tryPauseWithStateTransition()
break
default:
_toError("Cannot pause while in the '" + getState() + "' state")
break
}
}
function stop() {
switch (getState()) {
case MediaPlayerBase.STATE.STOPPED:
break
case MediaPlayerBase.STATE.BUFFERING:
case MediaPlayerBase.STATE.PLAYING:
case MediaPlayerBase.STATE.PAUSED:
case MediaPlayerBase.STATE.COMPLETE:
_stopPlayer()
_toStopped()
break
default:
_toError("Cannot stop while in the '" + getState() + "' state")
break
}
}
function reset() {
switch (getState()) {
case MediaPlayerBase.STATE.EMPTY:
break
case MediaPlayerBase.STATE.STOPPED:
case MediaPlayerBase.STATE.ERROR:
_toEmpty()
break
default:
_toError("Cannot reset while in the '" + getState() + "' state")
break
}
}
function getSource() {
return source
}
function getMimeType() {
return mimeType
}
function getCurrentTime() {
if (getState() === MediaPlayerBase.STATE.STOPPED) {
return undefined
} else {
return currentTime
}
}
function getSeekableRange() {
return range
}
function getDuration() {
if (range) {
return range.end
}
return undefined
}
function getState() {
return state
}
function getPlayerElement() {
return playerPlugin
}
function toPlaying() {
state = MediaPlayerBase.STATE.PLAYING
_emitEvent(MediaPlayerBase.EVENT.PLAYING)
}
function toPaused() {
state = MediaPlayerBase.STATE.PAUSED
_emitEvent(MediaPlayerBase.EVENT.PAUSED)
}
function _toStopped() {
currentTime = 0
range = undefined
state = MediaPlayerBase.STATE.STOPPED
_emitEvent(MediaPlayerBase.EVENT.STOPPED)
}
function _toBuffering() {
state = MediaPlayerBase.STATE.BUFFERING
_emitEvent(MediaPlayerBase.EVENT.BUFFERING)
}
function _toComplete() {
state = MediaPlayerBase.STATE.COMPLETE
_emitEvent(MediaPlayerBase.EVENT.COMPLETE)
}
function _toEmpty() {
_wipe()
state = MediaPlayerBase.STATE.EMPTY
}
function _toError(errorMessage) {
_wipe()
state = MediaPlayerBase.STATE.ERROR
_reportError(errorMessage)
throw new Error("ApiError: " + errorMessage)
}
function _onFinishedBuffering() {
if (getState() !== MediaPlayerBase.STATE.BUFFERING) {
return
}
if (deferSeekingTo === null) {
if (postBufferingState === MediaPlayerBase.STATE.PAUSED) {
_tryPauseWithStateTransition()
} else {
toPlaying()
}
}
}
function _onDeviceError(message) {
_reportError(message)
}
function _onDeviceBuffering() {
if (getState() === MediaPlayerBase.STATE.PLAYING) {
_toBuffering()
}
}
function _onEndOfMedia() {
_toComplete()
}
function _stopPlayer() {
playerPlugin.Stop()
currentTimeKnown = false
}
function _tryPauseWithStateTransition() {
let success = _isSuccessCode(playerPlugin.Pause())
if (success) {
toPaused()
}
tryingToPause = !success
}
function _onStatus() {
let state = getState()
if (state === MediaPlayerBase.STATE.PLAYING) {
_emitEvent(MediaPlayerBase.EVENT.STATUS)
}
}
function _onMetadata() {
range = {
start: 0,
end: playerPlugin.GetDuration() / 1000,
}
}
function _onCurrentTime(timeInMillis) {
currentTime = timeInMillis / 1000
_onStatus()
currentTimeKnown = true
if (deferSeekingTo !== null) {
_deferredSeek()
}
if (tryingToPause) {
_tryPauseWithStateTransition()
}
}
function _deferredSeek() {
const clampedTime = _getClampedTimeForPlayFrom(deferSeekingTo)
const isNearCurrentTime = _isNearToCurrentTime(clampedTime)
if (isNearCurrentTime) {
toPlaying()
deferSeekingTo = null
} else {
const seekResult = _seekTo(clampedTime)
if (seekResult) {
deferSeekingTo = null
}
}
}
function _getClampedTimeForPlayFrom(seconds) {
const clampedTime = getClampedTime(seconds)
if (clampedTime !== seconds) {
DebugTool.info(
"playFrom " +
seconds +
" clamped to " +
clampedTime +
" - seekable range is { start: " +
range.start +
", end: " +
range.end +
" }"
)
}
return clampedTime
}
function _onWindowHide() {
stop()
}
function _registerEventHandlers() {
window.SamsungMapleOnRenderError = () => _onDeviceError("Media element emitted OnRenderError")
playerPlugin.OnRenderError = "SamsungMapleOnRenderError"
window.SamsungMapleOnConnectionFailed = () => _onDeviceError("Media element emitted OnConnectionFailed")
playerPlugin.OnConnectionFailed = "SamsungMapleOnConnectionFailed"
window.SamsungMapleOnNetworkDisconnected = () => _onDeviceError("Media element emitted OnNetworkDisconnected")
playerPlugin.OnNetworkDisconnected = "SamsungMapleOnNetworkDisconnected"
window.SamsungMapleOnStreamNotFound = () => _onDeviceError("Media element emitted OnStreamNotFound")
playerPlugin.OnStreamNotFound = "SamsungMapleOnStreamNotFound"
window.SamsungMapleOnAuthenticationFailed = () => _onDeviceError("Media element emitted OnAuthenticationFailed")
playerPlugin.OnAuthenticationFailed = "SamsungMapleOnAuthenticationFailed"
window.SamsungMapleOnRenderingComplete = () => _onEndOfMedia()
playerPlugin.OnRenderingComplete = "SamsungMapleOnRenderingComplete"
window.SamsungMapleOnBufferingStart = () => _onDeviceBuffering()
playerPlugin.OnBufferingStart = "SamsungMapleOnBufferingStart"
window.SamsungMapleOnBufferingComplete = () => _onFinishedBuffering()
playerPlugin.OnBufferingComplete = "SamsungMapleOnBufferingComplete"
window.SamsungMapleOnStreamInfoReady = () => _onMetadata()
playerPlugin.OnStreamInfoReady = "SamsungMapleOnStreamInfoReady"
window.SamsungMapleOnCurrentPlayTime = (timeInMillis) => _onCurrentTime(timeInMillis)
playerPlugin.OnCurrentPlayTime = "SamsungMapleOnCurrentPlayTime"
window.addEventListener("hide", _onWindowHide, false)
window.addEventListener("unload", _onWindowHide, false)
}
function _unregisterEventHandlers() {
const eventHandlers = [
"SamsungMapleOnRenderError",
"SamsungMapleOnRenderingComplete",
"SamsungMapleOnBufferingStart",
"SamsungMapleOnBufferingComplete",
"SamsungMapleOnStreamInfoReady",
"SamsungMapleOnCurrentPlayTime",
"SamsungMapleOnConnectionFailed",
"SamsungMapleOnNetworkDisconnected",
"SamsungMapleOnStreamNotFound",
"SamsungMapleOnAuthenticationFailed",
]
for (let i = 0; i < eventHandlers.length; i++) {
const handler = eventHandlers[i]
const hook = handler.substring("SamsungMaple".length)
playerPlugin[hook] = undefined
delete window[handler]
}
window.removeEventListener("hide", _onWindowHide, false)
window.removeEventListener("unload", _onWindowHide, false)
}
function _wipe() {
_stopPlayer()
mediaType = undefined
source = undefined
mimeType = undefined
currentTime = undefined
range = undefined
deferSeekingTo = null
tryingToPause = false
currentTimeKnown = false
_unregisterEventHandlers()
}
function _seekTo(seconds) {
const offset = seconds - getCurrentTime()
const success = _isSuccessCode(_jump(offset))
if (success) {
currentTime = seconds
}
return success
}
function _seekToWithFailureStateTransition(seconds) {
const success = _seekTo(seconds)
if (!success) {
toPlaying()
}
}
function _jump(offsetSeconds) {
if (offsetSeconds > 0) {
return playerPlugin.JumpForward(offsetSeconds)
} else {
return playerPlugin.JumpBackward(Math.abs(offsetSeconds))
}
}
function _isHlsMimeType() {
const mime = mimeType.toLowerCase()
return mime === "application/vnd.apple.mpegurl" || mime === "application/x-mpegurl"
}
function _wrappedSource() {
let wrappedSource = source
if (_isHlsMimeType()) {
wrappedSource += "|COMPONENT=HLS"
}
return wrappedSource
}
function _reportError(errorMessage) {
DebugTool.info(errorMessage)
_emitEvent(MediaPlayerBase.EVENT.ERROR, { errorMessage: errorMessage })
}
function _setDisplayFullScreenForVideo() {
if (mediaType === MediaPlayerBase.TYPE.VIDEO) {
const dimensions = _getScreenSize()
playerPlugin.SetDisplayArea(0, 0, dimensions.width, dimensions.height)
}
}
function _getScreenSize() {
let w, h
if (typeof window.innerWidth === "number") {
w = window.innerWidth
h = window.innerHeight
} else {
const d = document.documentElement || document.body
h = d.clientHeight || d.offsetHeight
w = d.clientWidth || d.offsetWidth
}
return {
width: w,
height: h,
}
}
function _isSuccessCode(code) {
const samsung2010ErrorCode = -1
return code && code !== samsung2010ErrorCode
}
/**
* @constant {Number} Time (in seconds) compared to current time within which seeking has no effect.
* On a sample device (Samsung FoxP 2013), seeking by two seconds worked 90% of the time, but seeking
* by 2.5 seconds was always seen to work.
*/
const CURRENT_TIME_TOLERANCE = 2.5
function _isNearToCurrentTime(seconds) {
const currentTime = getCurrentTime()
const targetTime = getClampedTime(seconds)
return Math.abs(currentTime - targetTime) <= CURRENT_TIME_TOLERANCE
}
function getClampedTime(seconds) {
const range = getSeekableRange()
const CLAMP_OFFSET_FROM_END_OF_RANGE = 1.1
const nearToEnd = Math.max(range.end - CLAMP_OFFSET_FROM_END_OF_RANGE, range.start)
if (seconds < range.start) {
return range.start
} else if (seconds > nearToEnd) {
return nearToEnd
} else {
return seconds
}
}
function _emitEvent(eventType, eventLabels) {
const event = {
type: eventType,
currentTime: getCurrentTime(),
seekableRange: getSeekableRange(),
duration: getDuration(),
url: getSource(),
mimeType: getMimeType(),
state: getState(),
}
if (eventLabels) {
for (const key in eventLabels) {
if (eventLabels.hasOwnProperty(key)) {
event[key] = eventLabels[key]
}
}
}
for (let index = 0; index < eventCallbacks.length; index++) {
eventCallbacks[index](event)
}
}
return {
addEventCallback: (thisArg, newCallback) => {
eventCallback = (event) => {
newCallback.call(thisArg, event)
}
eventCallbacks.push(eventCallback)
},
removeEventCallback: (callback) => {
const index = eventCallbacks.indexOf(callback)
if (index !== -1) {
eventCallbacks.splice(index, 1)
}
},
removeAllEventCallbacks: () => {
eventCallbacks = []
},
initialiseMedia: initialiseMedia,
playFrom: playFrom,
beginPlayback: beginPlayback,
beginPlaybackFrom: beginPlaybackFrom,
resume: resume,
pause: pause,
stop: stop,
reset: reset,
getSeekableRange: getSeekableRange,
getState: getState,
getPlayerElement: getPlayerElement,
getSource: getSource,
getMimeType: getMimeType,
getCurrentTime: getCurrentTime,
getDuration: getDuration,
toPaused: toPaused,
toPlaying: toPlaying,
}
}
export default SamsungMaple