2020-08-16 20:24:45 +02:00
import browser from '../../scripts/browser' ;
import { appHost } from '../../components/apphost' ;
2020-08-14 08:46:34 +02:00
import loading from '../../components/loading/loading' ;
2020-08-16 20:24:45 +02:00
import dom from '../../scripts/dom' ;
import { playbackManager } from '../../components/playback/playbackmanager' ;
import { appRouter } from '../../components/appRouter' ;
2020-07-26 14:18:34 +02:00
import {
bindEventsToHlsPlayer ,
destroyHlsPlayer ,
destroyFlvPlayer ,
destroyCastPlayer ,
getCrossOriginValue ,
enableHlsJsPlayer ,
applySrc ,
2021-09-17 01:20:58 +03:00
resetSrc ,
2020-07-26 14:18:34 +02:00
playWithPromise ,
onEndedInternal ,
saveVolume ,
seekOnPlaybackStart ,
onErrorInternal ,
handleHlsJsMediaError ,
getSavedVolume ,
isValidDuration ,
getBufferedRanges
2020-08-16 20:24:45 +02:00
} from '../../components/htmlMediaHelper' ;
import itemHelper from '../../components/itemHelper' ;
import Screenfull from 'screenfull' ;
import globalize from '../../scripts/globalize' ;
2020-10-17 19:08:56 +01:00
import ServerConnections from '../../components/ServerConnections' ;
2020-10-18 13:53:12 +01:00
import profileBuilder from '../../scripts/browserDeviceProfile' ;
2020-11-30 14:38:03 -05:00
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings' ;
2022-04-12 16:22:00 -04:00
import { setBackdropTransparency , TRANSPARENCY _LEVEL } from '../../components/backdrop/backdrop' ;
2022-10-14 10:53:16 -04:00
import Events from '../../utils/events.ts' ;
2020-04-15 12:27:04 +02:00
2022-05-15 15:20:46 -04:00
/ * *
* Returns resolved URL .
* @ param { string } url - URL .
* @ returns { string } Resolved URL or ` url ` if resolving failed .
* /
function resolveUrl ( url ) {
return new Promise ( ( resolve ) => {
2022-05-22 05:27:54 -06:00
const xhr = new XMLHttpRequest ( ) ;
2022-05-15 15:20:46 -04:00
xhr . open ( 'HEAD' , url , true ) ;
xhr . onload = function ( ) {
resolve ( xhr . responseURL || url ) ;
} ;
xhr . onerror = function ( e ) {
console . error ( e ) ;
resolve ( url ) ;
} ;
xhr . send ( null ) ;
} ) ;
}
2020-04-15 12:27:04 +02:00
/* eslint-disable indent */
function tryRemoveElement ( elem ) {
const parentNode = elem . parentNode ;
2019-01-10 15:39:37 +03:00
if ( parentNode ) {
// Seeing crashes in edge webview
try {
parentNode . removeChild ( elem ) ;
} catch ( err ) {
2020-07-22 22:59:27 +02:00
console . error ( ` error removing dialog element: ${ err } ` ) ;
2019-01-10 15:39:37 +03:00
}
}
}
2018-10-23 01:05:09 +03:00
function enableNativeTrackSupport ( currentSrc , track ) {
2022-10-03 14:22:02 -04:00
if ( track ? . DeliveryMethod === 'Embed' ) {
return true ;
2019-01-10 15:39:37 +03:00
}
2022-10-03 14:22:02 -04:00
if ( browser . firefox && ( currentSrc || '' ) . toLowerCase ( ) . includes ( '.m3u8' ) ) {
return false ;
2019-01-10 15:39:37 +03:00
}
if ( browser . ps4 ) {
return false ;
}
if ( browser . web0s ) {
return false ;
}
// Edge is randomly not rendering subtitles
if ( browser . edge ) {
return false ;
}
2022-10-03 14:22:02 -04:00
if ( browser . iOS && ( browser . iosVersion || 10 ) < 10 ) {
2019-01-10 15:39:37 +03:00
// works in the browser but not the native app
2022-10-03 14:22:02 -04:00
return false ;
2019-01-10 15:39:37 +03:00
}
2018-10-23 01:05:09 +03:00
if ( track ) {
2020-07-21 22:22:16 +02:00
const format = ( track . Codec || '' ) . toLowerCase ( ) ;
2019-01-10 15:39:37 +03:00
if ( format === 'ssa' || format === 'ass' ) {
return false ;
}
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
return true ;
2018-10-23 01:05:09 +03:00
}
function requireHlsPlayer ( callback ) {
2020-08-16 20:24:45 +02:00
import ( 'hls.js' ) . then ( ( { default : hls } ) => {
2019-01-10 15:39:37 +03:00
window . Hls = hls ;
callback ( ) ;
} ) ;
2018-10-23 01:05:09 +03:00
}
function getMediaStreamAudioTracks ( mediaSource ) {
2019-01-10 15:39:37 +03:00
return mediaSource . MediaStreams . filter ( function ( s ) {
return s . Type === 'Audio' ;
} ) ;
2018-10-23 01:05:09 +03:00
}
function getMediaStreamTextTracks ( mediaSource ) {
2019-01-10 15:39:37 +03:00
return mediaSource . MediaStreams . filter ( function ( s ) {
return s . Type === 'Subtitle' ;
} ) ;
2018-10-23 01:05:09 +03:00
}
function zoomIn ( elem ) {
2020-07-27 19:58:57 +02:00
return new Promise ( resolve => {
2020-04-15 12:27:04 +02:00
const duration = 240 ;
2020-07-22 22:42:26 +02:00
elem . style . animation = ` htmlvideoplayer-zoomin ${ duration } ms ease-in normal ` ;
2019-01-10 15:39:37 +03:00
dom . addEventListener ( elem , dom . whichAnimationEvent ( ) , resolve , {
once : true
} ) ;
} ) ;
2018-10-23 01:05:09 +03:00
}
2020-04-09 02:28:07 +03:00
function normalizeTrackEventText ( text , useHtml ) {
2020-07-21 22:22:16 +02:00
const result = text . replace ( /\\N/gi , '\n' ) . replace ( /\r/gi , '' ) ;
2020-04-09 02:28:07 +03:00
return useHtml ? result . replace ( /\n/gi , '<br>' ) : result ;
2018-10-23 01:05:09 +03:00
}
function getTextTrackUrl ( track , item , format ) {
2019-01-10 15:39:37 +03:00
if ( itemHelper . isLocalItem ( item ) && track . Path ) {
return track . Path ;
}
2020-04-15 12:27:04 +02:00
let url = playbackManager . getSubtitleUrl ( track , item . ServerId ) ;
2019-01-10 15:39:37 +03:00
if ( format ) {
url = url . replace ( '.vtt' , format ) ;
}
return url ;
2018-10-23 01:05:09 +03:00
}
function getDefaultProfile ( ) {
2020-10-18 13:53:12 +01:00
return profileBuilder ( { } ) ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
export class HtmlVideoPlayer {
2020-07-22 22:42:26 +02:00
/ * *
* @ type { string }
* /
2020-08-01 04:07:00 +02:00
name ;
2020-07-22 22:42:26 +02:00
/ * *
* @ type { string }
* /
type = 'mediaplayer' ;
/ * *
* @ type { string }
* /
id = 'htmlvideoplayer' ;
/ * *
* Let any players created by plugins take priority
*
* @ type { number }
* /
priority = 1 ;
/ * *
* @ type { boolean }
* /
isFetching = false ;
/ * *
* @ type { HTMLDivElement | null | undefined }
* /
# videoDialog ;
/ * *
* @ type { number | undefined }
* /
# subtitleTrackIndexToSetOnPlaying ;
/ * *
* @ type { number | null }
* /
# audioTrackIndexToSetOnPlaying ;
/ * *
* @ type { null | undefined }
* /
# currentClock ;
/ * *
* @ type { any | null | undefined }
* /
# currentSubtitlesOctopus ;
/ * *
* @ type { null | undefined }
* /
# currentAssRenderer ;
/ * *
* @ type { number | undefined }
* /
# customTrackIndex ;
/ * *
* @ type { boolean | undefined }
* /
# showTrackOffset ;
/ * *
* @ type { number | undefined }
* /
# currentTrackOffset ;
/ * *
* @ type { HTMLElement | null | undefined }
* /
# videoSubtitlesElem ;
/ * *
* @ type { any | null | undefined }
* /
# currentTrackEvents ;
/ * *
* @ type { string [ ] | undefined }
* /
# supportedFeatures ;
/ * *
* @ type { HTMLVideoElement | null | undefined }
* /
# mediaElement ;
/ * *
* @ type { number }
* /
# fetchQueue = 0 ;
/ * *
* @ type { string | undefined }
* /
# currentSrc ;
/ * *
* @ type { boolean | undefined }
* /
# started ;
/ * *
* @ type { boolean | undefined }
* /
# timeUpdated ;
/ * *
* @ type { number | null | undefined }
* /
# currentTime ;
/ * *
* @ type { any | undefined }
* /
# flvPlayer ;
/ * *
* @ private ( used in other files )
* @ type { any | undefined }
* /
_hlsPlayer ;
/ * *
* @ private ( used in other files )
* @ type { any | null | undefined }
* /
_castPlayer ;
/ * *
* @ private ( used in other files )
* @ type { any | undefined }
* /
_currentPlayOptions ;
/ * *
* @ type { any | undefined }
* /
# lastProfile ;
2020-04-15 12:27:04 +02:00
constructor ( ) {
if ( browser . edgeUwp ) {
this . name = 'Windows Video Player' ;
} else {
this . name = 'Html Video Player' ;
}
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
currentSrc ( ) {
2020-07-22 22:42:26 +02:00
return this . # currentSrc ;
2020-04-15 12:27:04 +02:00
}
2019-09-12 21:24:16 +02:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
incrementFetchQueue ( ) {
2020-07-22 22:42:26 +02:00
if ( this . # fetchQueue <= 0 ) {
2020-04-15 12:27:04 +02:00
this . isFetching = true ;
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'beginFetch' ) ;
2019-09-12 21:24:16 +02:00
}
2020-07-22 22:42:26 +02:00
this . # fetchQueue ++ ;
2019-09-12 21:24:16 +02:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
decrementFetchQueue ( ) {
2020-07-22 22:42:26 +02:00
this . # fetchQueue -- ;
2019-09-12 21:24:16 +02:00
2020-07-22 22:42:26 +02:00
if ( this . # fetchQueue <= 0 ) {
2020-04-15 12:27:04 +02:00
this . isFetching = false ;
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'endFetch' ) ;
2019-09-12 21:24:16 +02:00
}
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
updateVideoUrl ( streamInfo ) {
2020-07-21 22:22:16 +02:00
const isHls = streamInfo . url . toLowerCase ( ) . includes ( '.m3u8' ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const mediaSource = streamInfo . mediaSource ;
const item = streamInfo . item ;
2019-01-10 15:39:37 +03:00
// Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts
// This will start the transcoding process before actually feeding the video url into the player
// Edit: Also seeing stalls from hls.js
if ( mediaSource && item && ! mediaSource . RunTimeTicks && isHls && streamInfo . playMethod === 'Transcode' && ( browser . iOS || browser . osx ) ) {
2020-07-21 22:22:16 +02:00
const hlsPlaylistUrl = streamInfo . url . replace ( 'master.m3u8' , 'live.m3u8' ) ;
2019-01-10 15:39:37 +03:00
loading . show ( ) ;
2020-07-22 22:42:26 +02:00
console . debug ( ` prefetching hls playlist: ${ hlsPlaylistUrl } ` ) ;
2019-01-10 15:39:37 +03:00
2020-10-17 19:08:56 +01:00
return ServerConnections . getApiClient ( item . ServerId ) . ajax ( {
2019-01-10 15:39:37 +03:00
type : 'GET' ,
2018-10-23 01:05:09 +03:00
url : hlsPlaylistUrl
2019-01-10 15:39:37 +03:00
} ) . then ( function ( ) {
2020-07-22 22:42:26 +02:00
console . debug ( ` completed prefetching hls playlist: ${ hlsPlaylistUrl } ` ) ;
2019-01-10 15:39:37 +03:00
loading . hide ( ) ;
streamInfo . url = hlsPlaylistUrl ;
} , function ( ) {
2020-07-22 22:42:26 +02:00
console . error ( ` error prefetching hls playlist: ${ hlsPlaylistUrl } ` ) ;
2019-01-10 15:39:37 +03:00
loading . hide ( ) ;
} ) ;
} else {
return Promise . resolve ( ) ;
2018-10-23 01:05:09 +03:00
}
}
2020-04-15 12:27:04 +02:00
play ( options ) {
2020-07-22 22:42:26 +02:00
this . # started = false ;
this . # timeUpdated = false ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
this . # currentTime = null ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
this . resetSubtitleOffset ( ) ;
2019-11-30 20:01:32 +01:00
2020-04-15 12:27:04 +02:00
return this . createMediaElement ( options ) . then ( elem => {
return this . updateVideoUrl ( options ) . then ( ( ) => {
return this . setCurrentSrc ( elem , options ) ;
2019-01-10 15:39:37 +03:00
} ) ;
} ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-07-22 22:42:26 +02:00
setSrcWithFlvJs ( elem , options , url ) {
2020-08-16 20:24:45 +02:00
return import ( 'flv.js' ) . then ( ( { default : flvjs } ) => {
2020-04-15 12:27:04 +02:00
const flvPlayer = flvjs . createPlayer ( {
2019-01-10 15:39:37 +03:00
type : 'flv' ,
2018-10-23 01:05:09 +03:00
url : url
2019-01-10 15:39:37 +03:00
} ,
2019-11-23 00:29:38 +09:00
{
seekType : 'range' ,
lazyLoad : false
} ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
flvPlayer . attachMediaElement ( elem ) ;
flvPlayer . load ( ) ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
this . # flvPlayer = flvPlayer ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
// This is needed in setCurrentTrackElement
2020-07-22 22:42:26 +02:00
this . # currentSrc = url ;
2020-04-15 12:27:04 +02:00
return flvPlayer . play ( ) ;
2019-01-10 15:39:37 +03:00
} ) ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-07-27 18:43:59 +02:00
setSrcWithHlsJs ( elem , options , url ) {
2020-04-19 23:23:46 +02:00
return new Promise ( ( resolve , reject ) => {
2020-11-30 14:38:03 -05:00
requireHlsPlayer ( async ( ) => {
2020-11-30 17:25:57 +08:00
let maxBufferLength = 30 ;
2020-12-12 16:44:57 +01:00
// Some browsers cannot handle huge fragments in high bitrate.
2020-11-30 17:25:57 +08:00
// This issue usually happens when using HWA encoders with a high bitrate setting.
// Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome.
// https://github.com/video-dev/hls.js/issues/876
2020-12-12 16:44:57 +01:00
if ( ( browser . chrome || browser . edgeChromium || browser . firefox ) && playbackManager . getMaxStreamingBitrate ( this ) >= 25000000 ) {
2020-11-30 17:25:57 +08:00
maxBufferLength = 6 ;
}
2020-11-30 14:38:03 -05:00
const includeCorsCredentials = await getIncludeCorsCredentials ( ) ;
2020-04-15 12:27:04 +02:00
const hls = new Hls ( {
2020-11-30 17:25:57 +08:00
manifestLoadingTimeOut : 20000 ,
maxBufferLength : maxBufferLength ,
2020-11-30 13:35:50 -05:00
xhrSetup ( xhr ) {
2020-11-30 14:38:03 -05:00
xhr . withCredentials = includeCorsCredentials ;
2020-11-30 13:35:50 -05:00
}
2018-10-23 01:05:09 +03:00
} ) ;
2019-01-10 15:39:37 +03:00
hls . loadSource ( url ) ;
hls . attachMedia ( elem ) ;
2020-07-26 14:18:34 +02:00
bindEventsToHlsPlayer ( this , hls , elem , this . onError , resolve , reject ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
this . _hlsPlayer = hls ;
2019-01-10 15:39:37 +03:00
// This is needed in setCurrentTrackElement
2020-07-22 22:42:26 +02:00
this . # currentSrc = url ;
2019-01-10 15:39:37 +03:00
} ) ;
} ) ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-11-30 14:38:03 -05:00
async setCurrentSrc ( elem , options ) {
2020-05-27 00:38:01 +02:00
elem . removeEventListener ( 'error' , this . onError ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
let val = options . url ;
2020-07-22 22:42:26 +02:00
console . debug ( ` playing url: ${ val } ` ) ;
2019-01-10 15:39:37 +03:00
// Convert to seconds
2020-04-15 12:27:04 +02:00
const seconds = ( options . playerStartPositionTicks || 0 ) / 10000000 ;
2019-01-10 15:39:37 +03:00
if ( seconds ) {
2020-07-22 22:42:26 +02:00
val += ` #t= ${ seconds } ` ;
2019-01-10 15:39:37 +03:00
}
2020-07-26 14:18:34 +02:00
destroyHlsPlayer ( this ) ;
destroyFlvPlayer ( this ) ;
destroyCastPlayer ( this ) ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
this . # subtitleTrackIndexToSetOnPlaying = options . mediaSource . DefaultSubtitleStreamIndex == null ? - 1 : options . mediaSource . DefaultSubtitleStreamIndex ;
if ( this . # subtitleTrackIndexToSetOnPlaying != null && this . # subtitleTrackIndexToSetOnPlaying >= 0 ) {
const initialSubtitleStream = options . mediaSource . MediaStreams [ this . # subtitleTrackIndexToSetOnPlaying ] ;
2019-01-10 15:39:37 +03:00
if ( ! initialSubtitleStream || initialSubtitleStream . DeliveryMethod === 'Encode' ) {
2020-07-22 22:42:26 +02:00
this . # subtitleTrackIndexToSetOnPlaying = - 1 ;
2019-01-10 15:39:37 +03:00
}
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
this . # audioTrackIndexToSetOnPlaying = options . playMethod === 'Transcode' ? null : options . mediaSource . DefaultAudioStreamIndex ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
this . _currentPlayOptions = options ;
2019-01-10 15:39:37 +03:00
2020-07-26 14:18:34 +02:00
const crossOrigin = getCrossOriginValue ( options . mediaSource ) ;
2019-01-10 15:39:37 +03:00
if ( crossOrigin ) {
elem . crossOrigin = crossOrigin ;
}
2018-10-23 01:05:09 +03:00
2020-07-27 22:07:10 +02:00
if ( enableHlsJsPlayer ( options . mediaSource . RunTimeTicks , 'Video' ) && val . includes ( '.m3u8' ) ) {
2020-07-27 18:43:59 +02:00
return this . setSrcWithHlsJs ( elem , options , val ) ;
2019-01-10 15:39:37 +03:00
} else if ( options . playMethod !== 'Transcode' && options . mediaSource . Container === 'flv' ) {
2020-07-22 22:42:26 +02:00
return this . setSrcWithFlvJs ( elem , options , val ) ;
2019-01-10 15:39:37 +03:00
} else {
elem . autoplay = true ;
2020-11-30 14:38:03 -05:00
const includeCorsCredentials = await getIncludeCorsCredentials ( ) ;
if ( includeCorsCredentials ) {
// Safari will not send cookies without this
elem . crossOrigin = 'use-credentials' ;
}
2020-05-07 21:04:26 -07:00
2020-07-26 14:18:34 +02:00
return applySrc ( elem , val , options ) . then ( ( ) => {
2020-07-22 22:42:26 +02:00
this . # currentSrc = val ;
2019-01-10 15:39:37 +03:00
2020-07-26 14:18:34 +02:00
return playWithPromise ( elem , this . onError ) ;
2019-01-10 15:39:37 +03:00
} ) ;
2018-10-23 01:05:09 +03:00
}
}
2020-04-15 12:27:04 +02:00
setSubtitleStreamIndex ( index ) {
this . setCurrentTrackElement ( index ) ;
}
2018-10-23 01:05:09 +03:00
2020-04-15 12:27:04 +02:00
resetSubtitleOffset ( ) {
2020-07-22 22:42:26 +02:00
this . # currentTrackOffset = 0 ;
this . # showTrackOffset = false ;
2020-04-15 12:27:04 +02:00
}
2019-11-30 20:01:32 +01:00
2020-04-15 12:27:04 +02:00
enableShowingSubtitleOffset ( ) {
2020-07-22 22:42:26 +02:00
this . # showTrackOffset = true ;
2020-04-15 12:27:04 +02:00
}
2019-04-08 20:21:16 +02:00
2020-04-15 12:27:04 +02:00
disableShowingSubtitleOffset ( ) {
2020-07-22 22:42:26 +02:00
this . # showTrackOffset = false ;
2020-04-15 12:27:04 +02:00
}
2019-04-08 20:21:16 +02:00
2020-04-15 12:27:04 +02:00
isShowingSubtitleOffsetEnabled ( ) {
2020-07-22 22:42:26 +02:00
return this . # showTrackOffset ;
2020-04-15 12:27:04 +02:00
}
2019-04-08 20:21:16 +02:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
getTextTrack ( ) {
2020-07-22 22:42:26 +02:00
const videoElement = this . # mediaElement ;
2019-12-07 20:29:14 +01:00
if ( videoElement ) {
return Array . from ( videoElement . textTracks )
2020-04-15 12:27:04 +02:00
. find ( function ( trackElement ) {
2019-12-09 21:37:05 +01:00
// get showing .vtt textTack
return trackElement . mode === 'showing' ;
} ) ;
2019-12-07 20:29:14 +01:00
} else {
2020-01-07 21:12:25 +01:00
return null ;
2019-12-07 20:29:14 +01:00
}
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
setSubtitleOffset ( offset ) {
const offsetValue = parseFloat ( offset ) ;
2019-03-30 22:11:39 +01:00
2019-10-13 14:10:51 +02:00
// if .ass currently rendering
2020-07-22 22:42:26 +02:00
if ( this . # currentSubtitlesOctopus ) {
2020-04-15 12:27:04 +02:00
this . updateCurrentTrackOffset ( offsetValue ) ;
2020-07-22 22:42:26 +02:00
this . # currentSubtitlesOctopus . timeOffset = ( this . _currentPlayOptions . transcodingOffsetTicks || 0 ) / 10000000 + offsetValue ;
2019-10-13 14:10:51 +02:00
} else {
2020-04-15 12:27:04 +02:00
const trackElement = this . getTextTrack ( ) ;
2019-12-07 20:29:14 +01:00
// if .vtt currently rendering
if ( trackElement ) {
2020-04-15 12:27:04 +02:00
this . setTextTrackSubtitleOffset ( trackElement , offsetValue ) ;
2020-07-22 22:42:26 +02:00
} else if ( this . # currentTrackEvents ) {
this . setTrackEventsSubtitleOffset ( this . # currentTrackEvents , offsetValue ) ;
2019-12-07 20:29:14 +01:00
} else {
2020-05-04 12:44:12 +02:00
console . debug ( 'No available track, cannot apply offset: ' , offsetValue ) ;
2019-12-07 20:29:14 +01:00
}
2019-10-13 14:10:51 +02:00
}
2020-04-15 12:27:04 +02:00
}
2019-03-30 22:11:39 +01:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
updateCurrentTrackOffset ( offsetValue ) {
let relativeOffset = offsetValue ;
const newTrackOffset = offsetValue ;
2020-07-22 22:42:26 +02:00
if ( this . # currentTrackOffset ) {
relativeOffset -= this . # currentTrackOffset ;
2019-03-30 22:11:39 +01:00
}
2020-07-22 22:42:26 +02:00
this . # currentTrackOffset = newTrackOffset ;
2019-03-30 22:11:39 +01:00
// relative to currentTrackOffset
return relativeOffset ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
setTextTrackSubtitleOffset ( currentTrack , offsetValue ) {
2019-11-23 00:29:38 +09:00
if ( currentTrack . cues ) {
2020-04-15 12:27:04 +02:00
offsetValue = this . updateCurrentTrackOffset ( offsetValue ) ;
2019-03-30 22:11:39 +01:00
Array . from ( currentTrack . cues )
2020-04-15 12:27:04 +02:00
. forEach ( function ( cue ) {
2019-11-23 00:29:38 +09:00
cue . startTime -= offsetValue ;
cue . endTime -= offsetValue ;
} ) ;
2019-03-30 22:11:39 +01:00
}
2020-01-07 21:12:25 +01:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
setTrackEventsSubtitleOffset ( trackEvents , offsetValue ) {
2020-01-07 21:12:25 +01:00
if ( Array . isArray ( trackEvents ) ) {
2020-04-15 12:27:04 +02:00
offsetValue = this . updateCurrentTrackOffset ( offsetValue ) * 1e7 ; // ticks
trackEvents . forEach ( function ( trackEvent ) {
2019-12-07 20:29:14 +01:00
trackEvent . StartPositionTicks -= offsetValue ;
trackEvent . EndPositionTicks -= offsetValue ;
} ) ;
2019-03-30 22:11:39 +01:00
}
}
2020-04-15 12:27:04 +02:00
getSubtitleOffset ( ) {
2020-07-22 22:42:26 +02:00
return this . # currentTrackOffset ;
2020-04-15 12:27:04 +02:00
}
2019-04-08 20:21:16 +02:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
isAudioStreamSupported ( stream , deviceProfile ) {
2020-07-21 22:22:16 +02:00
const codec = ( stream . Codec || '' ) . toLowerCase ( ) ;
2019-01-10 15:39:37 +03:00
if ( ! codec ) {
return true ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
if ( ! deviceProfile ) {
// This should never happen
return true ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const profiles = deviceProfile . DirectPlayProfiles || [ ] ;
2019-01-10 15:39:37 +03:00
return profiles . filter ( function ( p ) {
if ( p . Type === 'Video' ) {
if ( ! p . AudioCodec ) {
return true ;
}
2020-04-19 23:23:46 +02:00
return p . AudioCodec . toLowerCase ( ) . includes ( codec ) ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
return false ;
} ) . length > 0 ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
getSupportedAudioStreams ( ) {
2020-07-22 22:42:26 +02:00
const profile = this . # lastProfile ;
2019-01-10 15:39:37 +03:00
2020-04-19 17:38:03 +02:00
return getMediaStreamAudioTracks ( this . _currentPlayOptions . mediaSource ) . filter ( ( stream ) => {
2020-04-15 12:27:04 +02:00
return this . isAudioStreamSupported ( stream , profile ) ;
2019-01-10 15:39:37 +03:00
} ) ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
setAudioStreamIndex ( index ) {
const streams = this . getSupportedAudioStreams ( ) ;
2019-01-10 15:39:37 +03:00
if ( streams . length < 2 ) {
// If there's only one supported stream then trust that the player will handle it on it's own
return ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
let audioIndex = - 1 ;
2019-01-10 15:39:37 +03:00
2020-04-19 17:38:03 +02:00
for ( const stream of streams ) {
2019-01-10 15:39:37 +03:00
audioIndex ++ ;
if ( stream . Index === index ) {
break ;
}
}
if ( audioIndex === - 1 ) {
return ;
}
2020-07-22 22:42:26 +02:00
const elem = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( ! elem ) {
return ;
}
2020-07-30 00:44:33 +02:00
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks
2019-01-10 15:39:37 +03:00
2020-07-29 19:51:33 +02:00
/ * *
2020-07-30 00:41:11 +02:00
* @ type { ArrayLike < any > | any [ ] }
2020-07-29 19:51:33 +02:00
* /
2020-04-15 12:27:04 +02:00
const elemAudioTracks = elem . audioTracks || [ ] ;
2020-07-22 22:42:26 +02:00
console . debug ( ` found ${ elemAudioTracks . length } audio tracks ` ) ;
2019-01-10 15:39:37 +03:00
2020-07-29 19:51:33 +02:00
for ( const [ i , audioTrack ] of Array . from ( elemAudioTracks ) . entries ( ) ) {
2019-01-10 15:39:37 +03:00
if ( audioIndex === i ) {
2020-04-19 17:38:03 +02:00
console . debug ( ` setting audio track ${ i } to enabled ` ) ;
audioTrack . enabled = true ;
2019-01-10 15:39:37 +03:00
} else {
2020-04-19 17:38:03 +02:00
console . debug ( ` setting audio track ${ i } to disabled ` ) ;
audioTrack . enabled = false ;
2019-01-10 15:39:37 +03:00
}
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
stop ( destroyPlayer ) {
2020-07-22 22:42:26 +02:00
const elem = this . # mediaElement ;
const src = this . # currentSrc ;
2019-01-10 15:39:37 +03:00
if ( elem ) {
if ( src ) {
elem . pause ( ) ;
}
2020-07-26 14:18:34 +02:00
onEndedInternal ( this , elem , this . onError ) ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
this . destroyCustomTrack ( elem ) ;
2019-01-10 15:39:37 +03:00
2021-09-23 21:30:59 +03:00
if ( destroyPlayer ) {
this . destroy ( ) ;
}
2019-01-10 15:39:37 +03:00
return Promise . resolve ( ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
destroy ( ) {
2020-07-26 14:18:34 +02:00
destroyHlsPlayer ( this ) ;
destroyFlvPlayer ( this ) ;
2019-01-10 15:39:37 +03:00
2022-04-12 16:22:00 -04:00
setBackdropTransparency ( TRANSPARENCY _LEVEL . None ) ;
2020-09-03 01:04:21 +03:00
document . body . classList . remove ( 'hide-scroll' ) ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
const videoElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( videoElement ) {
2020-07-22 22:42:26 +02:00
this . # mediaElement = null ;
2020-04-15 12:27:04 +02:00
this . destroyCustomTrack ( videoElement ) ;
2020-05-27 00:38:01 +02:00
videoElement . removeEventListener ( 'timeupdate' , this . onTimeUpdate ) ;
videoElement . removeEventListener ( 'ended' , this . onEnded ) ;
videoElement . removeEventListener ( 'volumechange' , this . onVolumeChange ) ;
videoElement . removeEventListener ( 'pause' , this . onPause ) ;
videoElement . removeEventListener ( 'playing' , this . onPlaying ) ;
videoElement . removeEventListener ( 'play' , this . onPlay ) ;
videoElement . removeEventListener ( 'click' , this . onClick ) ;
videoElement . removeEventListener ( 'dblclick' , this . onDblClick ) ;
videoElement . removeEventListener ( 'waiting' , this . onWaiting ) ;
2021-09-12 01:28:56 +03:00
videoElement . removeEventListener ( 'error' , this . onError ) ; // bound in htmlMediaHelper
2019-01-10 15:39:37 +03:00
2021-09-17 01:20:58 +03:00
resetSrc ( videoElement ) ;
2019-01-10 15:39:37 +03:00
videoElement . parentNode . removeChild ( videoElement ) ;
}
2020-07-22 22:42:26 +02:00
const dlg = this . # videoDialog ;
2019-01-10 15:39:37 +03:00
if ( dlg ) {
2020-07-22 22:42:26 +02:00
this . # videoDialog = null ;
2019-01-10 15:39:37 +03:00
dlg . parentNode . removeChild ( dlg ) ;
}
2019-02-17 23:29:31 +01:00
2020-08-16 20:24:45 +02:00
if ( Screenfull . isEnabled ) {
Screenfull . exit ( ) ;
2020-07-29 09:28:06 -04:00
} else {
// iOS Safari
if ( document . webkitIsFullScreen && document . webkitCancelFullscreen ) {
document . webkitCancelFullscreen ( ) ;
}
2020-04-26 16:16:48 +02:00
}
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
2020-05-27 00:38:01 +02:00
* @ param e { Event } The event received from the ` <video> ` element
2020-04-15 12:27:04 +02:00
* /
2020-05-27 00:38:01 +02:00
onEnded = ( e ) => {
/ * *
* @ type { HTMLMediaElement }
* /
const elem = e . target ;
this . destroyCustomTrack ( elem ) ;
2020-07-26 14:18:34 +02:00
onEndedInternal ( this , elem , this . onError ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
2020-05-27 00:38:01 +02:00
* @ param e { Event } The event received from the ` <video> ` element
2020-04-15 12:27:04 +02:00
* /
2020-05-27 00:38:01 +02:00
onTimeUpdate = ( e ) => {
/ * *
* @ type { HTMLMediaElement }
* /
const elem = e . target ;
2019-05-22 23:06:39 -07:00
// get the player position and the transcoding offset
2020-05-27 00:38:01 +02:00
const time = elem . currentTime ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
if ( time && ! this . # timeUpdated ) {
this . # timeUpdated = true ;
2020-05-27 00:38:01 +02:00
this . ensureValidVideo ( elem ) ;
2019-01-10 15:39:37 +03:00
}
2020-07-22 22:42:26 +02:00
this . # currentTime = time ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const currentPlayOptions = this . _currentPlayOptions ;
2019-01-10 15:39:37 +03:00
// Not sure yet how this is coming up null since we never null it out, but it is causing app crashes
if ( currentPlayOptions ) {
2020-04-15 12:27:04 +02:00
let timeMs = time * 1000 ;
2019-01-10 15:39:37 +03:00
timeMs += ( ( currentPlayOptions . transcodingOffsetTicks || 0 ) / 10000 ) ;
2020-04-15 12:27:04 +02:00
this . updateSubtitleText ( timeMs ) ;
2019-01-10 15:39:37 +03:00
}
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'timeupdate' ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
2020-05-27 00:38:01 +02:00
* @ param e { Event } The event received from the ` <video> ` element
2020-04-15 12:27:04 +02:00
* /
2020-05-27 00:38:01 +02:00
onVolumeChange = ( e ) => {
/ * *
* @ type { HTMLMediaElement }
* /
const elem = e . target ;
2020-07-26 14:18:34 +02:00
saveVolume ( elem . volume ) ;
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'volumechange' ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-05-27 00:38:01 +02:00
onNavigatedToOsd = ( ) => {
2020-07-22 22:42:26 +02:00
const dlg = this . # videoDialog ;
2019-01-10 15:39:37 +03:00
if ( dlg ) {
dlg . classList . remove ( 'videoPlayerContainer-onTop' ) ;
2020-04-15 12:27:04 +02:00
this . onStartedAndNavigatedToOsd ( ) ;
2019-01-10 15:39:37 +03:00
}
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
onStartedAndNavigatedToOsd ( ) {
2019-01-10 15:39:37 +03:00
// If this causes a failure during navigation we end up in an awkward UI state
2020-07-22 22:42:26 +02:00
this . setCurrentTrackElement ( this . # subtitleTrackIndexToSetOnPlaying ) ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
if ( this . # audioTrackIndexToSetOnPlaying != null && this . canSetAudioStreamIndex ( ) ) {
this . setAudioStreamIndex ( this . # audioTrackIndexToSetOnPlaying ) ;
2019-01-10 15:39:37 +03:00
}
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
2020-05-27 00:38:01 +02:00
* @ param e { Event } The event received from the ` <video> ` element
2020-04-15 12:27:04 +02:00
* /
2020-05-27 00:38:01 +02:00
onPlaying = ( e ) => {
/ * *
* @ type { HTMLMediaElement }
* /
const elem = e . target ;
2020-07-22 22:42:26 +02:00
if ( ! this . # started ) {
this . # started = true ;
2020-05-27 00:38:01 +02:00
elem . removeAttribute ( 'controls' ) ;
2019-01-10 15:39:37 +03:00
loading . hide ( ) ;
2020-07-26 14:18:34 +02:00
seekOnPlaybackStart ( this , e . target , this . _currentPlayOptions . playerStartPositionTicks , ( ) => {
2020-07-22 22:42:26 +02:00
if ( this . # currentSubtitlesOctopus ) {
this . # currentSubtitlesOctopus . timeOffset = ( this . _currentPlayOptions . transcodingOffsetTicks || 0 ) / 10000000 + this . # currentTrackOffset ;
this . # currentSubtitlesOctopus . resize ( ) ;
this . # currentSubtitlesOctopus . resetRenderAheadCache ( false ) ;
2020-04-27 18:43:39 +03:00
}
2020-04-15 19:29:21 +03:00
} ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
if ( this . _currentPlayOptions . fullscreen ) {
2020-05-27 00:38:01 +02:00
appRouter . showVideoOsd ( ) . then ( this . onNavigatedToOsd ) ;
2019-01-10 15:39:37 +03:00
} else {
2022-04-12 16:22:00 -04:00
setBackdropTransparency ( TRANSPARENCY _LEVEL . Backdrop ) ;
2020-07-22 22:42:26 +02:00
this . # videoDialog . classList . remove ( 'videoPlayerContainer-onTop' ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
this . onStartedAndNavigatedToOsd ( ) ;
2019-01-10 15:39:37 +03:00
}
}
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'playing' ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-05-27 00:38:01 +02:00
onPlay = ( ) => {
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'unpause' ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
ensureValidVideo ( elem ) {
2020-07-22 22:42:26 +02:00
if ( elem !== this . # mediaElement ) {
2019-01-10 15:39:37 +03:00
return ;
}
if ( elem . videoWidth === 0 && elem . videoHeight === 0 ) {
2020-04-15 12:27:04 +02:00
const mediaSource = ( this . _currentPlayOptions || { } ) . mediaSource ;
2019-01-10 15:39:37 +03:00
// Only trigger this if there is media info
// Avoid triggering in situations where it might not actually have a video stream (audio only live tv channel)
if ( ! mediaSource || mediaSource . RunTimeTicks ) {
2020-07-26 14:18:34 +02:00
onErrorInternal ( this , 'mediadecodeerror' ) ;
2019-01-10 15:39:37 +03:00
}
}
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-05-27 00:38:01 +02:00
onClick = ( ) => {
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'click' ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-05-27 00:38:01 +02:00
onDblClick = ( ) => {
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'dblclick' ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-05-27 00:38:01 +02:00
onPause = ( ) => {
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'pause' ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
onWaiting ( ) {
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'waiting' ) ;
2020-04-01 17:53:14 +02:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
2020-05-27 00:38:01 +02:00
* @ param e { Event } The event received from the ` <video> ` element
2020-04-15 12:27:04 +02:00
* /
2020-05-27 00:38:01 +02:00
onError = ( e ) => {
/ * *
* @ type { HTMLMediaElement }
* /
2020-07-21 22:22:16 +02:00
const elem = e . target ;
2020-05-27 00:38:01 +02:00
const errorCode = elem . error ? ( elem . error . code || 0 ) : 0 ;
2020-07-21 22:22:16 +02:00
const errorMessage = elem . error ? ( elem . error . message || '' ) : '' ;
2020-07-22 22:42:26 +02:00
console . error ( ` media element error: ${ errorCode } ${ errorMessage } ` ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
let type ;
2019-01-10 15:39:37 +03:00
switch ( errorCode ) {
case 1 :
// MEDIA_ERR_ABORTED
// This will trigger when changing media while something is playing
return ;
case 2 :
// MEDIA_ERR_NETWORK
type = 'network' ;
break ;
case 3 :
// MEDIA_ERR_DECODE
2020-04-19 17:38:03 +02:00
if ( this . _hlsPlayer ) {
2020-07-26 14:18:34 +02:00
handleHlsJsMediaError ( this ) ;
2019-01-10 15:39:37 +03:00
return ;
} else {
type = 'mediadecodeerror' ;
}
break ;
case 4 :
// MEDIA_ERR_SRC_NOT_SUPPORTED
type = 'medianotsupported' ;
break ;
default :
// seeing cases where Edge is firing error events with no error code
// example is start playing something, then immediately change src to something else
return ;
}
2020-07-26 14:18:34 +02:00
onErrorInternal ( this , type ) ;
2020-08-01 04:07:00 +02:00
} ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
destroyCustomTrack ( videoElement ) {
2020-07-22 22:42:26 +02:00
if ( this . # videoSubtitlesElem ) {
const subtitlesContainer = this . # videoSubtitlesElem . parentNode ;
2019-01-10 15:39:37 +03:00
if ( subtitlesContainer ) {
tryRemoveElement ( subtitlesContainer ) ;
}
2020-07-22 22:42:26 +02:00
this . # videoSubtitlesElem = null ;
2019-01-10 15:39:37 +03:00
}
2020-07-22 22:42:26 +02:00
this . # currentTrackEvents = null ;
2019-01-10 15:39:37 +03:00
if ( videoElement ) {
2020-04-15 12:27:04 +02:00
const allTracks = videoElement . textTracks || [ ] ; // get list of tracks
2020-04-19 17:38:03 +02:00
for ( const track of allTracks ) {
if ( track . label . includes ( 'manualTrack' ) ) {
track . mode = 'disabled' ;
2019-01-10 15:39:37 +03:00
}
}
}
2020-07-22 22:42:26 +02:00
this . # customTrackIndex = - 1 ;
this . # currentClock = null ;
2020-04-15 12:27:04 +02:00
this . _currentAspectRatio = null ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
const octopus = this . # currentSubtitlesOctopus ;
2019-09-27 10:52:15 -04:00
if ( octopus ) {
octopus . dispose ( ) ;
}
2020-07-22 22:42:26 +02:00
this . # currentSubtitlesOctopus = null ;
2019-09-27 10:52:15 -04:00
2020-07-22 22:42:26 +02:00
const renderer = this . # currentAssRenderer ;
2019-01-10 15:39:37 +03:00
if ( renderer ) {
renderer . setEnabled ( false ) ;
}
2020-07-22 22:42:26 +02:00
this . # currentAssRenderer = null ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-07-27 18:54:04 +02:00
fetchSubtitlesUwp ( track ) {
2019-01-10 15:39:37 +03:00
return Windows . Storage . StorageFile . getFileFromPathAsync ( track . Path ) . then ( function ( storageFile ) {
2020-04-15 12:27:04 +02:00
return Windows . Storage . FileIO . readTextAsync ( storageFile ) ;
} ) . then ( function ( text ) {
return JSON . parse ( text ) ;
2019-01-10 15:39:37 +03:00
} ) ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
2020-07-22 22:42:26 +02:00
async fetchSubtitles ( track , item ) {
2019-01-10 15:39:37 +03:00
if ( window . Windows && itemHelper . isLocalItem ( item ) ) {
2020-04-15 12:27:04 +02:00
return this . fetchSubtitlesUwp ( track , item ) ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
this . incrementFetchQueue ( ) ;
2020-07-22 22:42:26 +02:00
try {
const response = await fetch ( getTextTrackUrl ( track , item , '.js' ) ) ;
2019-01-10 15:39:37 +03:00
2020-07-27 22:07:10 +02:00
if ( ! response . ok ) {
throw new Error ( response ) ;
}
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
return response . json ( ) ;
} finally {
this . decrementFetchQueue ( ) ;
}
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
setTrackForDisplay ( videoElement , track ) {
2019-01-10 15:39:37 +03:00
if ( ! track ) {
2020-04-15 12:27:04 +02:00
this . destroyCustomTrack ( videoElement ) ;
2019-01-10 15:39:37 +03:00
return ;
}
2019-05-22 23:06:39 -07:00
// skip if already playing this track
2020-07-22 22:42:26 +02:00
if ( this . # customTrackIndex === track . Index ) {
2019-01-10 15:39:37 +03:00
return ;
}
2020-04-15 12:27:04 +02:00
this . resetSubtitleOffset ( ) ;
2020-04-19 17:38:03 +02:00
const item = this . _currentPlayOptions . item ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
this . destroyCustomTrack ( videoElement ) ;
2020-07-22 22:42:26 +02:00
this . # customTrackIndex = track . Index ;
2020-04-15 12:27:04 +02:00
this . renderTracksEvents ( videoElement , track , item ) ;
2019-01-10 15:39:37 +03:00
}
2018-10-23 01:05:09 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
renderSsaAss ( videoElement , track , item ) {
2022-05-14 02:19:13 -04:00
const supportedFonts = [ 'application/vnd.ms-opentype' , 'application/x-truetype-font' , 'font/otf' , 'font/ttf' , 'font/woff' , 'font/woff2' ] ;
2020-07-31 00:35:23 +08:00
const avaliableFonts = [ ] ;
2020-04-19 17:38:03 +02:00
const attachments = this . _currentPlayOptions . mediaSource . MediaAttachments || [ ] ;
2021-02-26 20:01:21 +03:00
const apiClient = ServerConnections . getApiClient ( item ) ;
2022-01-07 22:07:59 +02:00
attachments . forEach ( i => {
// we only require font files and ignore embedded media attachments like covers as there are cases where ffmpeg fails to extract those
if ( supportedFonts . includes ( i . MimeType ) ) {
// embedded font url
avaliableFonts . push ( apiClient . getUrl ( i . DeliveryUrl ) ) ;
}
2020-07-24 21:55:33 +08:00
} ) ;
2020-07-31 00:30:05 +08:00
const fallbackFontList = apiClient . getUrl ( '/FallbackFont/Fonts' , {
2020-07-24 21:55:33 +08:00
api _key : apiClient . accessToken ( )
} ) ;
2020-04-19 23:23:46 +02:00
const htmlVideoPlayer = this ;
2020-04-15 12:27:04 +02:00
const options = {
2019-09-27 10:52:15 -04:00
video : videoElement ,
subUrl : getTextTrackUrl ( track , item ) ,
2020-07-24 21:55:33 +08:00
fonts : avaliableFonts ,
2020-07-22 22:42:26 +02:00
workerUrl : ` ${ appRouter . baseUrl ( ) } /libraries/subtitles-octopus-worker.js ` ,
legacyWorkerUrl : ` ${ appRouter . baseUrl ( ) } /libraries/subtitles-octopus-worker-legacy.js ` ,
2020-04-15 12:27:04 +02:00
onError ( ) {
2021-09-23 21:12:16 +03:00
// HACK: Clear JavascriptSubtitlesOctopus: it gets disposed when an error occurs
htmlVideoPlayer . # currentSubtitlesOctopus = null ;
2021-09-23 22:20:03 +03:00
// HACK: Give JavascriptSubtitlesOctopus time to dispose itself
setTimeout ( ( ) => {
onErrorInternal ( htmlVideoPlayer , 'mediadecodeerror' ) ;
} , 0 ) ;
2020-03-29 22:04:36 +03:00
} ,
2020-04-15 12:27:04 +02:00
timeOffset : ( this . _currentPlayOptions . transcodingOffsetTicks || 0 ) / 10000000 ,
2020-03-29 22:04:36 +03:00
// new octopus options; override all, even defaults
2022-05-15 22:10:42 -04:00
renderMode : 'wasm-blend' ,
2020-03-29 22:04:36 +03:00
dropAllAnimations : false ,
libassMemoryLimit : 40 ,
libassGlyphLimit : 40 ,
targetFps : 24 ,
2022-05-15 22:10:42 -04:00
prescaleFactor : 0.8 ,
prescaleHeightLimit : 1080 ,
maxRenderHeight : 2160 ,
2020-03-29 22:04:36 +03:00
resizeVariation : 0.2 ,
renderAhead : 90
2019-09-27 10:52:15 -04:00
} ;
2022-05-15 22:10:42 -04:00
import ( '@jellyfin/libass-wasm' ) . then ( ( { default : SubtitlesOctopus } ) => {
2022-05-15 15:20:46 -04:00
Promise . all ( [
apiClient . getNamedConfiguration ( 'encoding' ) ,
// Worker in Tizen 5 doesn't resolve relative path with async request
resolveUrl ( options . workerUrl ) ,
resolveUrl ( options . legacyWorkerUrl )
] ) . then ( ( [ config , workerUrl , legacyWorkerUrl ] ) => {
options . workerUrl = workerUrl ;
options . legacyWorkerUrl = legacyWorkerUrl ;
2020-07-24 21:55:33 +08:00
if ( config . EnableFallbackFont ) {
2020-11-19 17:21:24 -05:00
apiClient . getJSON ( fallbackFontList ) . then ( ( fontFiles = [ ] ) => {
2020-11-19 17:36:01 -05:00
fontFiles . forEach ( font => {
2020-07-31 00:30:05 +08:00
const fontUrl = apiClient . getUrl ( ` /FallbackFont/Fonts/ ${ font . Name } ` , {
2020-07-29 03:04:13 +08:00
api _key : apiClient . accessToken ( )
} ) ;
2020-11-19 17:36:01 -05:00
avaliableFonts . push ( fontUrl ) ;
2020-07-29 03:04:13 +08:00
} ) ;
2020-07-31 00:30:05 +08:00
this . # currentSubtitlesOctopus = new SubtitlesOctopus ( options ) ;
2020-07-29 03:04:13 +08:00
} ) ;
} else {
2020-07-31 00:30:05 +08:00
this . # currentSubtitlesOctopus = new SubtitlesOctopus ( options ) ;
2020-07-24 21:55:33 +08:00
}
} ) ;
2019-09-27 10:52:15 -04:00
} ) ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
requiresCustomSubtitlesElement ( ) {
2019-01-10 15:39:37 +03:00
// after a system update, ps4 isn't showing anything when creating a track element dynamically
// going to have to do it ourselves
if ( browser . ps4 ) {
return true ;
}
2022-09-12 13:38:59 -04:00
if ( browser . web0s ) {
2019-01-10 15:39:37 +03:00
return true ;
}
if ( browser . edge ) {
return true ;
}
if ( browser . iOS ) {
2020-04-15 12:27:04 +02:00
const userAgent = navigator . userAgent . toLowerCase ( ) ;
2019-01-10 15:39:37 +03:00
// works in the browser but not the native app
2020-04-19 23:23:46 +02:00
if ( ( userAgent . includes ( 'os 9' ) || userAgent . includes ( 'os 8' ) ) && ! userAgent . includes ( 'safari' ) ) {
2019-01-10 15:39:37 +03:00
return true ;
}
}
return false ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
renderSubtitlesWithCustomElement ( videoElement , track , item ) {
this . fetchSubtitles ( track , item ) . then ( ( data ) => {
2020-07-22 22:42:26 +02:00
if ( ! this . # videoSubtitlesElem ) {
2020-07-21 22:22:16 +02:00
const subtitlesContainer = document . createElement ( 'div' ) ;
2019-01-10 15:39:37 +03:00
subtitlesContainer . classList . add ( 'videoSubtitles' ) ;
subtitlesContainer . innerHTML = '<div class="videoSubtitlesInner"></div>' ;
2020-07-22 22:42:26 +02:00
this . # videoSubtitlesElem = subtitlesContainer . querySelector ( '.videoSubtitlesInner' ) ;
this . setSubtitleAppearance ( subtitlesContainer , this . # videoSubtitlesElem ) ;
2019-01-10 15:39:37 +03:00
videoElement . parentNode . appendChild ( subtitlesContainer ) ;
2020-07-22 22:42:26 +02:00
this . # currentTrackEvents = data . TrackEvents ;
2019-01-10 15:39:37 +03:00
}
} ) ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
setSubtitleAppearance ( elem , innerElem ) {
2020-08-16 20:24:45 +02:00
Promise . all ( [ import ( '../../scripts/settings/userSettings' ) , import ( '../../components/subtitlesettings/subtitleappearancehelper' ) ] ) . then ( ( [ userSettings , subtitleAppearanceHelper ] ) => {
2019-01-10 15:39:37 +03:00
subtitleAppearanceHelper . applyStyles ( {
text : innerElem ,
window : elem
} , userSettings . getSubtitleAppearanceSettings ( ) ) ;
} ) ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
getCueCss ( appearance , selector ) {
2020-07-22 22:42:26 +02:00
return ` ${ selector } ::cue {
2020-07-30 17:49:07 +03:00
$ { appearance . text . map ( ( s ) => s . value !== undefined && s . value !== '' ? ` ${ s . name } : ${ s . value } !important; ` : '' ) . join ( '' ) }
2020-07-22 22:42:26 +02:00
} ` ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
setCueAppearance ( ) {
2020-08-16 20:24:45 +02:00
Promise . all ( [ import ( '../../scripts/settings/userSettings' ) , import ( '../../components/subtitlesettings/subtitleappearancehelper' ) ] ) . then ( ( [ userSettings , subtitleAppearanceHelper ] ) => {
2020-07-22 22:42:26 +02:00
const elementId = ` ${ this . id } -cuestyle ` ;
2019-01-10 15:39:37 +03:00
2020-07-22 22:42:26 +02:00
let styleElem = document . querySelector ( ` # ${ elementId } ` ) ;
2019-01-10 15:39:37 +03:00
if ( ! styleElem ) {
styleElem = document . createElement ( 'style' ) ;
styleElem . id = elementId ;
document . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( styleElem ) ;
}
2020-07-30 17:49:07 +03:00
styleElem . innerHTML = this . getCueCss ( subtitleAppearanceHelper . getStyles ( userSettings . getSubtitleAppearanceSettings ( ) ) , '.htmlvideoplayer' ) ;
2019-01-10 15:39:37 +03:00
} ) ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
renderTracksEvents ( videoElement , track , item ) {
2019-01-10 15:39:37 +03:00
if ( ! itemHelper . isLocalItem ( item ) || track . IsExternal ) {
2020-07-21 22:22:16 +02:00
const format = ( track . Codec || '' ) . toLowerCase ( ) ;
2019-01-10 15:39:37 +03:00
if ( format === 'ssa' || format === 'ass' ) {
2020-04-15 12:27:04 +02:00
this . renderSsaAss ( videoElement , track , item ) ;
2019-01-10 15:39:37 +03:00
return ;
}
2020-04-15 12:27:04 +02:00
if ( this . requiresCustomSubtitlesElement ( ) ) {
this . renderSubtitlesWithCustomElement ( videoElement , track , item ) ;
2019-01-10 15:39:37 +03:00
return ;
}
}
2020-04-15 12:27:04 +02:00
let trackElement = null ;
2019-10-11 15:48:28 +02:00
if ( videoElement . textTracks && videoElement . textTracks . length > 0 ) {
trackElement = videoElement . textTracks [ 0 ] ;
2019-01-10 15:39:37 +03:00
2019-10-11 15:48:28 +02:00
// This throws an error in IE, but is fine in chrome
// In IE it's not necessary anyway because changing the src seems to be enough
try {
trackElement . mode = 'showing' ;
while ( trackElement . cues . length ) {
trackElement . removeCue ( trackElement . cues [ 0 ] ) ;
}
} catch ( e ) {
2020-02-16 03:44:43 +01:00
console . error ( 'error removing cue from textTrack' ) ;
2019-01-10 15:39:37 +03:00
}
2019-10-11 15:48:28 +02:00
trackElement . mode = 'disabled' ;
} else {
// There is a function addTextTrack but no function for removeTextTrack
// Therefore we add ONE element and replace its cue data
trackElement = videoElement . addTextTrack ( 'subtitles' , 'manualTrack' , 'und' ) ;
}
2019-01-10 15:39:37 +03:00
2019-10-11 15:48:28 +02:00
// download the track json
2020-04-15 12:27:04 +02:00
this . fetchSubtitles ( track , item ) . then ( function ( data ) {
2020-08-16 20:24:45 +02:00
import ( '../../scripts/settings/userSettings' ) . then ( ( userSettings ) => {
2020-07-07 01:06:47 +03:00
// show in ui
2020-07-30 17:49:07 +03:00
console . debug ( ` downloaded ${ data . TrackEvents . length } track events ` ) ;
2020-07-07 01:06:47 +03:00
const subtitleAppearance = userSettings . getSubtitleAppearanceSettings ( ) ;
2020-07-30 23:36:52 +03:00
const cueLine = parseInt ( subtitleAppearance . verticalPosition , 10 ) ;
2019-01-10 15:39:37 +03:00
2020-07-07 01:06:47 +03:00
// add some cues to show the text
// in safari, the cues need to be added before setting the track mode to showing
2020-07-30 17:49:07 +03:00
for ( const trackEvent of data . TrackEvents ) {
const trackCueObject = window . VTTCue || window . TextTrackCue ;
const cue = new trackCueObject ( trackEvent . StartPositionTicks / 10000000 , trackEvent . EndPositionTicks / 10000000 , normalizeTrackEventText ( trackEvent . Text , false ) ) ;
2019-01-10 15:39:37 +03:00
2020-07-07 01:06:47 +03:00
if ( cue . line === 'auto' ) {
cue . line = cueLine ;
}
2019-01-10 15:39:37 +03:00
2020-07-07 01:06:47 +03:00
trackElement . addCue ( cue ) ;
2020-07-30 17:49:07 +03:00
}
2020-07-07 01:06:47 +03:00
trackElement . mode = 'showing' ;
2019-01-10 15:39:37 +03:00
} ) ;
2019-10-11 15:48:28 +02:00
} ) ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
updateSubtitleText ( timeMs ) {
2020-07-22 22:42:26 +02:00
const clock = this . # currentClock ;
2019-01-10 15:39:37 +03:00
if ( clock ) {
try {
clock . seek ( timeMs / 1000 ) ;
} catch ( err ) {
2020-07-22 22:42:26 +02:00
console . error ( ` error in libjass: ${ err } ` ) ;
2019-01-10 15:39:37 +03:00
}
return ;
}
2020-07-22 22:42:26 +02:00
const trackEvents = this . # currentTrackEvents ;
const subtitleTextElement = this . # videoSubtitlesElem ;
2019-01-10 15:39:37 +03:00
if ( trackEvents && subtitleTextElement ) {
2020-04-15 12:27:04 +02:00
const ticks = timeMs * 10000 ;
let selectedTrackEvent ;
2020-04-19 17:38:03 +02:00
for ( const trackEvent of trackEvents ) {
if ( trackEvent . StartPositionTicks <= ticks && trackEvent . EndPositionTicks >= ticks ) {
selectedTrackEvent = trackEvent ;
2019-01-10 15:39:37 +03:00
break ;
}
}
if ( selectedTrackEvent && selectedTrackEvent . Text ) {
2020-04-09 02:28:07 +03:00
subtitleTextElement . innerHTML = normalizeTrackEventText ( selectedTrackEvent . Text , true ) ;
2019-01-10 15:39:37 +03:00
subtitleTextElement . classList . remove ( 'hide' ) ;
} else {
subtitleTextElement . classList . add ( 'hide' ) ;
}
}
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
setCurrentTrackElement ( streamIndex ) {
2020-07-22 22:42:26 +02:00
console . debug ( ` setting new text track index to: ${ streamIndex } ` ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const mediaStreamTextTracks = getMediaStreamTextTracks ( this . _currentPlayOptions . mediaSource ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
let track = streamIndex === - 1 ? null : mediaStreamTextTracks . filter ( function ( t ) {
2019-01-10 15:39:37 +03:00
return t . Index === streamIndex ;
} ) [ 0 ] ;
2020-07-22 22:42:26 +02:00
this . setTrackForDisplay ( this . # mediaElement , track ) ;
if ( enableNativeTrackSupport ( this . # currentSrc , track ) ) {
2019-01-10 15:39:37 +03:00
if ( streamIndex !== - 1 ) {
2020-04-15 12:27:04 +02:00
this . setCueAppearance ( ) ;
2019-01-10 15:39:37 +03:00
}
} else {
// null these out to disable the player's native display (handled below)
streamIndex = - 1 ;
track = null ;
}
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
createMediaElement ( options ) {
2020-07-21 22:22:16 +02:00
const dlg = document . querySelector ( '.videoPlayerContainer' ) ;
2019-01-10 15:39:37 +03:00
if ( ! dlg ) {
2021-01-26 15:48:00 -05:00
return import ( './style.scss' ) . then ( ( ) => {
2019-01-10 15:39:37 +03:00
loading . show ( ) ;
2022-10-16 16:04:37 +02:00
const newdlg = document . createElement ( 'div' ) ;
newdlg . setAttribute ( 'dir' , 'ltr' ) ;
2019-01-10 15:39:37 +03:00
2022-10-16 16:04:37 +02:00
newdlg . classList . add ( 'videoPlayerContainer' ) ;
2019-01-10 15:39:37 +03:00
if ( options . fullscreen ) {
2022-10-16 16:04:37 +02:00
newdlg . classList . add ( 'videoPlayerContainer-onTop' ) ;
2019-01-10 15:39:37 +03:00
}
2020-07-21 22:22:16 +02:00
let html = '' ;
2020-07-16 22:19:09 +02:00
const cssClass = 'htmlvideoplayer' ;
2019-01-10 15:39:37 +03:00
// Can't autoplay in these browsers so we need to use the full controls, at least until playback starts
if ( ! appHost . supports ( 'htmlvideoautoplay' ) ) {
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" controls="controls" webkit-playsinline playsinline>' ;
2022-06-26 20:58:23 -04:00
} else if ( browser . web0s ) {
// in webOS, setting preload auto allows resuming videos
html += '<video class="' + cssClass + '" preload="auto" autoplay="autoplay" webkit-playsinline playsinline>' ;
2019-01-10 15:39:37 +03:00
} else {
// Chrome 35 won't play with preload none
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" webkit-playsinline playsinline>' ;
}
html += '</video>' ;
2022-10-16 16:04:37 +02:00
newdlg . innerHTML = html ;
const videoElement = newdlg . querySelector ( 'video' ) ;
2019-01-10 15:39:37 +03:00
2020-07-26 14:18:34 +02:00
videoElement . volume = getSavedVolume ( ) ;
2020-04-15 12:27:04 +02:00
videoElement . addEventListener ( 'timeupdate' , this . onTimeUpdate ) ;
videoElement . addEventListener ( 'ended' , this . onEnded ) ;
videoElement . addEventListener ( 'volumechange' , this . onVolumeChange ) ;
videoElement . addEventListener ( 'pause' , this . onPause ) ;
videoElement . addEventListener ( 'playing' , this . onPlaying ) ;
videoElement . addEventListener ( 'play' , this . onPlay ) ;
videoElement . addEventListener ( 'click' , this . onClick ) ;
videoElement . addEventListener ( 'dblclick' , this . onDblClick ) ;
videoElement . addEventListener ( 'waiting' , this . onWaiting ) ;
2020-05-08 00:07:59 +02:00
if ( options . backdropUrl ) {
videoElement . poster = options . backdropUrl ;
}
2018-10-23 01:05:09 +03:00
2022-10-16 16:04:37 +02:00
document . body . insertBefore ( newdlg , document . body . firstChild ) ;
this . # videoDialog = newdlg ;
2020-07-22 22:42:26 +02:00
this . # mediaElement = videoElement ;
2018-10-23 01:05:09 +03:00
2021-09-14 20:27:02 +02:00
delete this . forcedFullscreen ;
2020-09-26 22:48:55 +03:00
if ( options . fullscreen ) {
2020-11-21 23:04:42 +03:00
// At this point, we must hide the scrollbar placeholder, so it's not being displayed while the item is being loaded
document . body . classList . add ( 'hide-scroll' ) ;
2020-09-26 22:48:55 +03:00
2021-09-14 20:27:02 +02:00
// Enter fullscreen in the webOS browser to hide the top bar
2021-09-11 21:03:23 +02:00
if ( ! window . NativeShell && browser . web0s && Screenfull . isEnabled ) {
2021-09-14 20:27:02 +02:00
Screenfull . request ( ) . then ( ( ) => {
this . forcedFullscreen = true ;
} ) ;
2021-09-04 13:12:48 +02:00
return videoElement ;
2021-07-03 19:42:08 +02:00
}
2021-09-14 20:27:02 +02:00
2021-07-03 19:42:08 +02:00
// don't animate on smart tv's, too slow
if ( ! browser . slow && browser . supportsCssAnimation ( ) ) {
2022-10-16 16:04:37 +02:00
return zoomIn ( newdlg ) . then ( function ( ) {
2021-07-03 19:42:08 +02:00
return videoElement ;
} ) ;
}
2019-01-10 15:39:37 +03:00
}
2021-09-11 17:28:22 +02:00
return videoElement ;
2019-01-10 15:39:37 +03:00
} ) ;
} else {
2020-09-26 22:48:55 +03:00
if ( options . fullscreen ) {
2021-09-16 19:19:21 +02:00
// we need to hide scrollbar when starting playback from page with animated background
2020-11-21 23:04:42 +03:00
document . body . classList . add ( 'hide-scroll' ) ;
2021-09-16 17:59:37 +02:00
// Enter fullscreen in the webOS browser to hide the top bar
if ( ! this . forcedFullscreen && ! window . NativeShell && browser . web0s && Screenfull . isEnabled ) {
Screenfull . request ( ) . then ( ( ) => {
this . forcedFullscreen = true ;
} ) ;
}
2020-09-26 22:48:55 +03:00
}
2020-04-15 12:27:04 +02:00
return Promise . resolve ( dlg . querySelector ( 'video' ) ) ;
2018-10-23 01:05:09 +03:00
}
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
canPlayMediaType ( mediaType ) {
2019-01-10 15:39:37 +03:00
return ( mediaType || '' ) . toLowerCase ( ) === 'video' ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
supportsPlayMethod ( playMethod , item ) {
2019-01-10 15:39:37 +03:00
if ( appHost . supportsPlayMethod ) {
return appHost . supportsPlayMethod ( playMethod , item ) ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
return true ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
getDeviceProfile ( item , options ) {
return HtmlVideoPlayer . getDeviceProfileInternal ( item , options ) . then ( ( profile ) => {
2020-07-22 22:42:26 +02:00
this . # lastProfile = profile ;
2019-01-10 15:39:37 +03:00
return profile ;
} ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
static getDeviceProfileInternal ( item , options ) {
2019-01-10 15:39:37 +03:00
if ( appHost . getDeviceProfile ) {
return appHost . getDeviceProfile ( item , options ) ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
return getDefaultProfile ( ) ;
}
2020-04-15 12:27:04 +02:00
/ * *
* @ private
* /
static getSupportedFeatures ( ) {
const list = [ ] ;
2019-01-10 15:39:37 +03:00
2020-07-21 22:22:16 +02:00
const video = document . createElement ( 'video' ) ;
2020-08-31 12:37:01 -04:00
if (
// Check non-standard Safari PiP support
typeof video . webkitSupportsPresentationMode === 'function' && video . webkitSupportsPresentationMode ( 'picture-in-picture' ) && typeof video . webkitSetPresentationMode === 'function'
2022-10-04 17:31:48 -04:00
// Check non-standard Windows PiP support
|| ( window . Windows
&& Windows . UI . ViewManagement . ApplicationView . getForCurrentView ( )
. isViewModeSupported ( Windows . UI . ViewManagement . ApplicationViewMode . compactOverlay ) )
2020-08-31 12:37:01 -04:00
// Check standard PiP support
|| document . pictureInPictureEnabled
) {
2019-01-10 15:39:37 +03:00
list . push ( 'PictureInPicture' ) ;
2018-10-23 01:05:09 +03:00
}
2020-01-10 11:31:03 -05:00
if ( browser . safari || browser . iOS || browser . iPad ) {
2020-04-05 13:48:10 +02:00
list . push ( 'AirPlay' ) ;
2020-01-10 11:31:03 -05:00
}
2020-05-05 12:01:43 +02:00
if ( typeof video . playbackRate === 'number' ) {
list . push ( 'PlaybackRate' ) ;
2020-04-01 17:53:14 +02:00
}
2019-01-10 15:39:37 +03:00
list . push ( 'SetBrightness' ) ;
2020-05-04 12:44:12 +02:00
list . push ( 'SetAspectRatio' ) ;
2018-10-23 01:05:09 +03:00
2019-01-10 15:39:37 +03:00
return list ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
supports ( feature ) {
2020-07-22 22:42:26 +02:00
if ( ! this . # supportedFeatures ) {
this . # supportedFeatures = HtmlVideoPlayer . getSupportedFeatures ( ) ;
2019-01-10 15:39:37 +03:00
}
2020-07-22 22:42:26 +02:00
return this . # supportedFeatures . includes ( feature ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
// Save this for when playback stops, because querying the time at that point might return 0
2020-04-15 12:27:04 +02:00
currentTime ( val ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2018-10-23 01:05:09 +03:00
if ( mediaElement ) {
2019-01-10 15:39:37 +03:00
if ( val != null ) {
mediaElement . currentTime = val / 1000 ;
return ;
}
2020-07-22 22:42:26 +02:00
const currentTime = this . # currentTime ;
2019-01-10 15:39:37 +03:00
if ( currentTime ) {
return currentTime * 1000 ;
}
return ( mediaElement . currentTime || 0 ) * 1000 ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-07-27 18:54:04 +02:00
duration ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2018-10-23 01:05:09 +03:00
if ( mediaElement ) {
2020-04-15 12:27:04 +02:00
const duration = mediaElement . duration ;
2020-07-26 14:18:34 +02:00
if ( isValidDuration ( duration ) ) {
2019-01-10 15:39:37 +03:00
return duration * 1000 ;
}
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
return null ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-07-27 18:54:04 +02:00
canSetAudioStreamIndex ( ) {
2019-01-10 15:39:37 +03:00
if ( browser . tizen || browser . orsay ) {
return true ;
}
2020-07-22 22:42:26 +02:00
const video = this . # mediaElement ;
2022-10-03 14:22:02 -04:00
return ! ! video ? . audioTracks ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
static onPictureInPictureError ( err ) {
2020-07-22 22:42:26 +02:00
console . error ( ` Picture in picture error: ${ err } ` ) ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
setPictureInPictureEnabled ( isEnabled ) {
2020-07-22 22:42:26 +02:00
const video = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( document . pictureInPictureEnabled ) {
if ( video ) {
if ( isEnabled ) {
2020-04-15 12:27:04 +02:00
video . requestPictureInPicture ( ) . catch ( HtmlVideoPlayer . onPictureInPictureError ) ;
2019-01-10 15:39:37 +03:00
} else {
2020-04-15 12:27:04 +02:00
document . exitPictureInPicture ( ) . catch ( HtmlVideoPlayer . onPictureInPictureError ) ;
2019-01-10 15:39:37 +03:00
}
}
2019-05-22 23:06:39 -07:00
} else if ( window . Windows ) {
2019-01-10 15:39:37 +03:00
this . isPip = isEnabled ;
if ( isEnabled ) {
Windows . UI . ViewManagement . ApplicationView . getForCurrentView ( ) . tryEnterViewModeAsync ( Windows . UI . ViewManagement . ApplicationViewMode . compactOverlay ) ;
2019-11-23 00:29:38 +09:00
} else {
2019-01-10 15:39:37 +03:00
Windows . UI . ViewManagement . ApplicationView . getForCurrentView ( ) . tryEnterViewModeAsync ( Windows . UI . ViewManagement . ApplicationViewMode . default ) ;
}
2019-05-22 23:06:39 -07:00
} else {
2020-05-04 12:44:12 +02:00
if ( video && video . webkitSupportsPresentationMode && typeof video . webkitSetPresentationMode === 'function' ) {
video . webkitSetPresentationMode ( isEnabled ? 'picture-in-picture' : 'inline' ) ;
2019-01-10 15:39:37 +03:00
}
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
isPictureInPictureEnabled ( ) {
2019-01-10 15:39:37 +03:00
if ( document . pictureInPictureEnabled ) {
2020-07-22 22:42:26 +02:00
return ! ! document . pictureInPictureElement ;
2019-05-22 23:06:39 -07:00
} else if ( window . Windows ) {
2019-01-10 15:39:37 +03:00
return this . isPip || false ;
2019-05-22 23:06:39 -07:00
} else {
2020-07-22 22:42:26 +02:00
const video = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( video ) {
2020-05-04 12:44:12 +02:00
return video . webkitPresentationMode === 'picture-in-picture' ;
2019-01-10 15:39:37 +03:00
}
}
return false ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-11-21 13:20:44 +08:00
isAirPlayEnabled ( ) {
2020-01-10 11:31:03 -05:00
if ( document . AirPlayEnabled ) {
2020-07-22 22:42:26 +02:00
return ! ! document . AirplayElement ;
2020-01-10 11:31:03 -05:00
}
return false ;
2020-04-15 12:27:04 +02:00
}
2020-01-10 11:31:03 -05:00
2020-04-15 12:27:04 +02:00
setAirPlayEnabled ( isEnabled ) {
2020-07-22 22:42:26 +02:00
const video = this . # mediaElement ;
2020-01-10 11:31:03 -05:00
if ( document . AirPlayEnabled ) {
if ( video ) {
if ( isEnabled ) {
2020-02-26 01:58:15 -05:00
video . requestAirPlay ( ) . catch ( function ( err ) {
2020-05-04 12:44:12 +02:00
console . error ( 'Error requesting AirPlay' , err ) ;
2020-02-26 01:58:15 -05:00
} ) ;
2020-01-10 11:31:03 -05:00
} else {
2020-02-26 01:58:15 -05:00
document . exitAirPLay ( ) . catch ( function ( err ) {
2020-05-04 12:44:12 +02:00
console . error ( 'Error exiting AirPlay' , err ) ;
2020-02-26 01:58:15 -05:00
} ) ;
2020-01-10 11:31:03 -05:00
}
}
} else {
video . webkitShowPlaybackTargetPicker ( ) ;
}
2020-04-15 12:27:04 +02:00
}
2020-01-10 11:31:03 -05:00
2020-04-15 12:27:04 +02:00
setBrightness ( val ) {
2020-07-22 22:42:26 +02:00
const elem = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
2018-10-23 01:05:09 +03:00
if ( elem ) {
2019-01-10 15:39:37 +03:00
val = Math . max ( 0 , val ) ;
val = Math . min ( 100 , val ) ;
2020-04-15 12:27:04 +02:00
let rawValue = val ;
2018-10-23 01:05:09 +03:00
rawValue = Math . max ( 20 , rawValue ) ;
2019-01-10 15:39:37 +03:00
2020-07-21 22:22:16 +02:00
const cssValue = rawValue >= 100 ? 'none' : ( rawValue / 100 ) ;
2020-07-22 22:42:26 +02:00
elem . style [ '-webkit-filter' ] = ` brightness( ${ cssValue } ) ` ;
elem . style . filter = ` brightness( ${ cssValue } ) ` ;
2019-01-10 15:39:37 +03:00
elem . brightnessValue = val ;
2020-09-08 02:05:02 -04:00
Events . trigger ( this , 'brightnesschange' ) ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
getBrightness ( ) {
2020-07-22 22:42:26 +02:00
const elem = this . # mediaElement ;
2018-10-23 01:05:09 +03:00
if ( elem ) {
2020-04-15 12:27:04 +02:00
const val = elem . brightnessValue ;
2019-01-10 15:39:37 +03:00
return val == null ? 100 : val ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
seekable ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2018-10-23 01:05:09 +03:00
if ( mediaElement ) {
2020-04-15 12:27:04 +02:00
const seekable = mediaElement . seekable ;
2018-10-23 01:05:09 +03:00
if ( seekable && seekable . length ) {
2020-04-15 12:27:04 +02:00
let start = seekable . start ( 0 ) ;
let end = seekable . end ( 0 ) ;
2019-01-10 15:39:37 +03:00
2020-07-26 14:18:34 +02:00
if ( ! isValidDuration ( start ) ) {
2019-01-10 15:39:37 +03:00
start = 0 ;
}
2020-07-26 14:18:34 +02:00
if ( ! isValidDuration ( end ) ) {
2019-01-10 15:39:37 +03:00
end = 0 ;
}
return ( end - start ) > 0 ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
return false ;
2018-10-23 01:05:09 +03:00
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
pause ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
mediaElement . pause ( ) ;
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
// This is a retry after error
2020-04-15 12:27:04 +02:00
resume ( ) {
2022-10-05 15:31:15 -04:00
this . unpause ( ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
unpause ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
mediaElement . play ( ) ;
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
paused ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
return mediaElement . paused ;
}
return false ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
setPlaybackRate ( value ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2020-04-01 17:53:14 +02:00
if ( mediaElement ) {
mediaElement . playbackRate = value ;
}
2020-07-21 22:22:16 +02:00
}
2020-04-01 17:53:14 +02:00
2020-04-15 12:27:04 +02:00
getPlaybackRate ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2020-04-01 17:53:14 +02:00
if ( mediaElement ) {
return mediaElement . playbackRate ;
}
return null ;
2020-07-21 22:22:16 +02:00
}
2020-04-01 17:53:14 +02:00
2020-05-22 22:23:18 -06:00
getSupportedPlaybackRates ( ) {
return [ {
name : '0.5x' ,
id : 0.5
} , {
name : '0.75x' ,
id : 0.75
} , {
name : '1x' ,
id : 1.0
} , {
name : '1.25x' ,
id : 1.25
} , {
name : '1.5x' ,
id : 1.5
} , {
name : '1.75x' ,
id : 1.75
} , {
name : '2x' ,
id : 2.0
} ] ;
}
2020-04-15 12:27:04 +02:00
setVolume ( val ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
2021-08-10 16:16:52 -07:00
mediaElement . volume = Math . pow ( val / 100 , 3 ) ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
getVolume ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
2021-08-10 16:16:52 -07:00
return Math . min ( Math . round ( Math . pow ( mediaElement . volume , 1 / 3 ) * 100 ) , 100 ) ;
2019-01-10 15:39:37 +03:00
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
volumeUp ( ) {
2019-01-10 15:39:37 +03:00
this . setVolume ( Math . min ( this . getVolume ( ) + 2 , 100 ) ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
volumeDown ( ) {
2019-01-10 15:39:37 +03:00
this . setVolume ( Math . max ( this . getVolume ( ) - 2 , 0 ) ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
setMute ( mute ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
mediaElement . muted = mute ;
}
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
isMuted ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
return mediaElement . muted ;
}
return false ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
setAspectRatio ( val ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-11-20 15:13:15 +01:00
if ( mediaElement ) {
2020-07-30 16:07:13 +02:00
if ( val === 'auto' ) {
2020-05-04 12:44:12 +02:00
mediaElement . style . removeProperty ( 'object-fit' ) ;
2019-11-20 15:13:15 +01:00
} else {
2020-05-04 12:44:12 +02:00
mediaElement . style [ 'object-fit' ] = val ;
2019-11-20 15:13:15 +01:00
}
}
2020-04-05 13:48:10 +02:00
this . _currentAspectRatio = val ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
getAspectRatio ( ) {
2020-05-04 12:44:12 +02:00
return this . _currentAspectRatio || 'auto' ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
getSupportedAspectRatios ( ) {
2019-11-20 15:13:15 +01:00
return [ {
2020-11-17 22:55:48 +08:00
name : globalize . translate ( 'Auto' ) ,
2020-05-04 12:44:12 +02:00
id : 'auto'
2019-11-20 15:13:15 +01:00
} , {
2020-11-17 22:55:48 +08:00
name : globalize . translate ( 'AspectRatioCover' ) ,
2020-05-04 12:44:12 +02:00
id : 'cover'
2019-11-20 15:13:15 +01:00
} , {
2020-11-17 22:55:48 +08:00
name : globalize . translate ( 'AspectRatioFill' ) ,
2020-05-04 12:44:12 +02:00
id : 'fill'
2020-04-05 13:48:10 +02:00
} ] ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
togglePictureInPicture ( ) {
2019-01-10 15:39:37 +03:00
return this . setPictureInPictureEnabled ( ! this . isPictureInPictureEnabled ( ) ) ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
toggleAirPlay ( ) {
2020-01-10 11:31:03 -05:00
return this . setAirPlayEnabled ( ! this . isAirPlayEnabled ( ) ) ;
2020-04-15 12:27:04 +02:00
}
2020-01-10 11:31:03 -05:00
2020-04-15 12:27:04 +02:00
getBufferedRanges ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2019-01-10 15:39:37 +03:00
if ( mediaElement ) {
2020-07-26 14:18:34 +02:00
return getBufferedRanges ( this , mediaElement ) ;
2019-01-10 15:39:37 +03:00
}
return [ ] ;
2020-04-15 12:27:04 +02:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
getStats ( ) {
2020-07-22 22:42:26 +02:00
const mediaElement = this . # mediaElement ;
2020-04-15 12:27:04 +02:00
const playOptions = this . _currentPlayOptions || [ ] ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const categories = [ ] ;
2019-01-10 15:39:37 +03:00
if ( ! mediaElement ) {
return Promise . resolve ( {
categories : categories
} ) ;
}
2020-04-15 12:27:04 +02:00
const mediaCategory = {
2018-10-23 01:05:09 +03:00
stats : [ ] ,
2019-01-10 15:39:37 +03:00
type : 'media'
2018-10-23 01:05:09 +03:00
} ;
2019-01-10 15:39:37 +03:00
categories . push ( mediaCategory ) ;
if ( playOptions . url ) {
// create an anchor element (note: no need to append this element to the document)
2020-07-21 22:22:16 +02:00
let link = document . createElement ( 'a' ) ;
2019-01-10 15:39:37 +03:00
// set href to any path
link . setAttribute ( 'href' , playOptions . url ) ;
2020-07-21 22:22:16 +02:00
const protocol = ( link . protocol || '' ) . replace ( ':' , '' ) ;
2019-01-10 15:39:37 +03:00
if ( protocol ) {
mediaCategory . stats . push ( {
2020-05-04 12:44:12 +02:00
label : globalize . translate ( 'LabelProtocol' ) ,
2019-01-10 15:39:37 +03:00
value : protocol
} ) ;
}
link = null ;
}
2020-07-23 22:54:34 +02:00
if ( this . _hlsPlayer ) {
2019-01-10 15:39:37 +03:00
mediaCategory . stats . push ( {
2020-05-04 12:44:12 +02:00
label : globalize . translate ( 'LabelStreamType' ) ,
2019-01-10 15:39:37 +03:00
value : 'HLS'
} ) ;
} else {
mediaCategory . stats . push ( {
2020-05-04 12:44:12 +02:00
label : globalize . translate ( 'LabelStreamType' ) ,
2019-01-10 15:39:37 +03:00
value : 'Video'
} ) ;
}
2020-04-15 12:27:04 +02:00
const videoCategory = {
2018-10-23 01:05:09 +03:00
stats : [ ] ,
2019-01-10 15:39:37 +03:00
type : 'video'
2018-10-23 01:05:09 +03:00
} ;
categories . push ( videoCategory ) ;
2019-01-10 15:39:37 +03:00
2022-02-11 00:40:42 +03:00
const devicePixelRatio = window . devicePixelRatio || 1 ;
2020-04-15 12:27:04 +02:00
const rect = mediaElement . getBoundingClientRect ? mediaElement . getBoundingClientRect ( ) : { } ;
2022-02-11 00:40:42 +03:00
let height = Math . round ( rect . height * devicePixelRatio ) ;
let width = Math . round ( rect . width * devicePixelRatio ) ;
2019-01-10 15:39:37 +03:00
// Don't show player dimensions on smart TVs because the app UI could be lower resolution than the video and this causes users to think there is a problem
if ( width && height && ! browser . tv ) {
videoCategory . stats . push ( {
2020-05-04 12:44:12 +02:00
label : globalize . translate ( 'LabelPlayerDimensions' ) ,
2020-07-22 22:42:26 +02:00
value : ` ${ width } x ${ height } `
2019-01-10 15:39:37 +03:00
} ) ;
}
height = mediaElement . videoHeight ;
width = mediaElement . videoWidth ;
if ( width && height ) {
videoCategory . stats . push ( {
2020-05-04 12:44:12 +02:00
label : globalize . translate ( 'LabelVideoResolution' ) ,
2020-07-22 22:42:26 +02:00
value : ` ${ width } x ${ height } `
2019-01-10 15:39:37 +03:00
} ) ;
}
if ( mediaElement . getVideoPlaybackQuality ) {
2020-04-15 12:27:04 +02:00
const playbackQuality = mediaElement . getVideoPlaybackQuality ( ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const droppedVideoFrames = playbackQuality . droppedVideoFrames || 0 ;
2018-10-23 01:05:09 +03:00
videoCategory . stats . push ( {
2020-05-04 12:44:12 +02:00
label : globalize . translate ( 'LabelDroppedFrames' ) ,
2018-10-23 01:05:09 +03:00
value : droppedVideoFrames
} ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const corruptedVideoFrames = playbackQuality . corruptedVideoFrames || 0 ;
2018-10-23 01:05:09 +03:00
videoCategory . stats . push ( {
2020-05-04 12:44:12 +02:00
label : globalize . translate ( 'LabelCorruptedFrames' ) ,
2018-10-23 01:05:09 +03:00
value : corruptedVideoFrames
2019-01-10 15:39:37 +03:00
} ) ;
2018-10-23 01:05:09 +03:00
}
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const audioCategory = {
2018-10-23 01:05:09 +03:00
stats : [ ] ,
2019-01-10 15:39:37 +03:00
type : 'audio'
2018-10-23 01:05:09 +03:00
} ;
categories . push ( audioCategory ) ;
2019-01-10 15:39:37 +03:00
2020-04-15 12:27:04 +02:00
const sinkId = mediaElement . sinkId ;
2019-01-10 15:39:37 +03:00
if ( sinkId ) {
audioCategory . stats . push ( {
label : 'Sink Id:' ,
value : sinkId
} ) ;
}
return Promise . resolve ( {
2018-10-23 01:05:09 +03:00
categories : categories
2019-01-10 15:39:37 +03:00
} ) ;
2020-04-15 12:27:04 +02:00
}
}
/* eslint-enable indent */
2019-01-10 15:39:37 +03:00
2020-04-19 23:23:46 +02:00
export default HtmlVideoPlayer ;