2015-05-15 05:03:05 +00:00
|
|
|
sortSources = (sources) ->
|
|
|
|
if not sources
|
|
|
|
console.error('sortSources() called with null source list')
|
|
|
|
return []
|
|
|
|
|
2017-07-01 23:50:30 +00:00
|
|
|
qualities = ['2160', '1440', '1080', '720', '540', '480', '360', '240']
|
2015-05-15 05:03:05 +00:00
|
|
|
pref = String(USEROPTS.default_quality)
|
2015-10-01 01:26:23 +00:00
|
|
|
if USEROPTS.default_quality == 'best'
|
2017-07-01 23:50:30 +00:00
|
|
|
pref = '2160'
|
2015-05-15 05:03:05 +00:00
|
|
|
idx = qualities.indexOf(pref)
|
|
|
|
if idx < 0
|
2017-08-14 04:48:50 +00:00
|
|
|
idx = 5 # 480p
|
2015-05-15 05:03:05 +00:00
|
|
|
|
2015-08-04 02:02:56 +00:00
|
|
|
qualityOrder = qualities.slice(idx).concat(qualities.slice(0, idx).reverse())
|
2017-09-04 16:44:30 +00:00
|
|
|
qualityOrder.unshift('auto')
|
2015-05-15 05:03:05 +00:00
|
|
|
sourceOrder = []
|
|
|
|
flvOrder = []
|
|
|
|
for quality in qualityOrder
|
|
|
|
if quality of sources
|
|
|
|
flv = []
|
|
|
|
nonflv = []
|
|
|
|
sources[quality].forEach((source) ->
|
2015-06-28 16:42:21 +00:00
|
|
|
source.quality = quality
|
2015-07-03 18:24:21 +00:00
|
|
|
if source.contentType == 'video/flv'
|
2015-05-15 05:03:05 +00:00
|
|
|
flv.push(source)
|
|
|
|
else
|
|
|
|
nonflv.push(source)
|
|
|
|
)
|
|
|
|
sourceOrder = sourceOrder.concat(nonflv)
|
|
|
|
flvOrder = flvOrder.concat(flv)
|
|
|
|
|
|
|
|
return sourceOrder.concat(flvOrder).map((source) ->
|
2015-07-03 18:24:21 +00:00
|
|
|
type: source.contentType
|
2015-05-15 05:03:05 +00:00
|
|
|
src: source.link
|
2017-11-28 06:10:00 +00:00
|
|
|
res: source.quality
|
|
|
|
label: getSourceLabel(source)
|
2015-05-15 05:03:05 +00:00
|
|
|
)
|
|
|
|
|
2017-09-04 16:44:30 +00:00
|
|
|
getSourceLabel = (source) ->
|
2017-11-28 06:10:00 +00:00
|
|
|
if source.res is 'auto'
|
2017-09-04 16:44:30 +00:00
|
|
|
return 'auto'
|
|
|
|
else
|
2017-11-28 06:10:00 +00:00
|
|
|
return "#{source.quality}p #{source.contentType.split('/')[1]}"
|
2017-09-04 16:44:30 +00:00
|
|
|
|
2015-07-01 16:38:01 +00:00
|
|
|
waitUntilDefined(window, 'videojs', =>
|
|
|
|
videojs.options.flash.swf = '/video-js.swf'
|
|
|
|
)
|
|
|
|
|
2021-10-17 23:37:57 +00:00
|
|
|
hasAnyTextTracks = (data) ->
|
|
|
|
ntracks = data?.meta?.textTracks?.length ? 0
|
|
|
|
return ntracks > 0
|
|
|
|
|
2015-05-15 05:03:05 +00:00
|
|
|
window.VideoJSPlayer = class VideoJSPlayer extends Player
|
2015-04-24 02:40:08 +00:00
|
|
|
constructor: (data) ->
|
2015-05-15 05:03:05 +00:00
|
|
|
if not (this instanceof VideoJSPlayer)
|
|
|
|
return new VideoJSPlayer(data)
|
|
|
|
|
2016-08-20 17:59:20 +00:00
|
|
|
@load(data)
|
2015-05-15 05:03:05 +00:00
|
|
|
|
2015-07-03 18:24:21 +00:00
|
|
|
loadPlayer: (data) ->
|
2015-05-15 05:03:05 +00:00
|
|
|
waitUntilDefined(window, 'videojs', =>
|
2017-08-22 03:06:07 +00:00
|
|
|
attrs =
|
|
|
|
width: '100%'
|
|
|
|
height: '100%'
|
|
|
|
|
2021-10-17 23:37:57 +00:00
|
|
|
if @mediaType == 'cm' and hasAnyTextTracks(data)
|
2017-08-22 03:06:07 +00:00
|
|
|
attrs.crossorigin = 'anonymous'
|
|
|
|
|
2015-05-15 05:03:05 +00:00
|
|
|
video = $('<video/>')
|
|
|
|
.addClass('video-js vjs-default-skin embed-responsive-item')
|
2017-08-22 03:06:07 +00:00
|
|
|
.attr(attrs)
|
2015-05-15 05:03:05 +00:00
|
|
|
removeOld(video)
|
|
|
|
|
2016-08-12 04:07:06 +00:00
|
|
|
@sources = sortSources(data.meta.direct)
|
|
|
|
if @sources.length == 0
|
2015-06-28 16:42:21 +00:00
|
|
|
console.error('VideoJSPlayer::constructor(): data.meta.direct
|
|
|
|
has no sources!')
|
2015-05-15 05:03:05 +00:00
|
|
|
@mediaType = null
|
|
|
|
return
|
|
|
|
|
2016-08-12 04:07:06 +00:00
|
|
|
@sourceIdx = 0
|
2015-05-15 05:03:05 +00:00
|
|
|
|
2017-08-09 03:35:17 +00:00
|
|
|
# TODO: Refactor VideoJSPlayer to use a preLoad()/load()/postLoad() pattern
|
|
|
|
# VideoJSPlayer should provide the core functionality and logic for specific
|
|
|
|
# dependent player types (gdrive) should be an extension
|
2015-07-25 08:19:32 +00:00
|
|
|
if data.meta.gdrive_subtitles
|
|
|
|
data.meta.gdrive_subtitles.available.forEach((subt) ->
|
2015-07-26 19:28:43 +00:00
|
|
|
label = subt.lang_original
|
|
|
|
if subt.name
|
|
|
|
label += " (#{subt.name})"
|
2015-07-25 08:19:32 +00:00
|
|
|
$('<track/>').attr(
|
|
|
|
src: "/gdvtt/#{data.id}/#{subt.lang}/#{subt.name}.vtt?\
|
|
|
|
vid=#{data.meta.gdrive_subtitles.vid}"
|
|
|
|
kind: 'subtitles'
|
|
|
|
srclang: subt.lang
|
2015-07-26 19:28:43 +00:00
|
|
|
label: label
|
2015-07-25 08:19:32 +00:00
|
|
|
).appendTo(video)
|
|
|
|
)
|
|
|
|
|
2017-08-09 03:35:17 +00:00
|
|
|
if data.meta.textTracks
|
|
|
|
data.meta.textTracks.forEach((track) ->
|
|
|
|
label = track.name
|
2020-11-02 03:37:52 +00:00
|
|
|
attrs =
|
2017-08-09 03:35:17 +00:00
|
|
|
src: track.url
|
|
|
|
kind: 'subtitles'
|
|
|
|
type: track.type
|
|
|
|
label: label
|
2020-11-02 03:37:52 +00:00
|
|
|
|
|
|
|
if track.default? and track.default
|
|
|
|
attrs.default = ''
|
|
|
|
|
|
|
|
$('<track/>').attr(attrs).appendTo(video)
|
2017-08-09 03:35:17 +00:00
|
|
|
)
|
|
|
|
|
2017-07-01 23:50:30 +00:00
|
|
|
@player = videojs(video[0],
|
2017-11-28 06:10:00 +00:00
|
|
|
# https://github.com/Dash-Industry-Forum/dash.js/issues/2184
|
|
|
|
autoplay: @sources[0].type != 'application/dash+xml',
|
2017-07-01 23:50:30 +00:00
|
|
|
controls: true,
|
|
|
|
plugins:
|
|
|
|
videoJsResolutionSwitcher:
|
2017-11-28 06:10:00 +00:00
|
|
|
default: @sources[0].res
|
2017-07-01 23:50:30 +00:00
|
|
|
)
|
2015-05-15 05:03:05 +00:00
|
|
|
@player.ready(=>
|
2017-11-28 06:10:00 +00:00
|
|
|
# Have to use updateSrc instead of <source> tags
|
|
|
|
# see: https://github.com/videojs/video.js/issues/3428
|
|
|
|
@player.updateSrc(@sources)
|
2016-08-12 04:07:06 +00:00
|
|
|
@player.on('error', =>
|
|
|
|
err = @player.error()
|
|
|
|
if err and err.code == 4
|
|
|
|
console.error('Caught error, trying next source')
|
2017-11-28 06:10:00 +00:00
|
|
|
# Does this really need to be done manually?
|
2016-08-12 04:07:06 +00:00
|
|
|
@sourceIdx++
|
|
|
|
if @sourceIdx < @sources.length
|
|
|
|
@player.src(@sources[@sourceIdx])
|
|
|
|
else
|
|
|
|
console.error('Out of sources, video will not play')
|
2021-08-08 16:49:20 +00:00
|
|
|
if @mediaType is 'gd'
|
|
|
|
if not window.hasDriveUserscript
|
|
|
|
window.promptToInstallDriveUserscript()
|
|
|
|
else
|
|
|
|
window.tellUserNotToContactMeAboutThingsThatAreNotSupported()
|
2016-08-12 04:07:06 +00:00
|
|
|
)
|
2015-07-05 20:50:34 +00:00
|
|
|
@setVolume(VOLUME)
|
2015-05-15 05:03:05 +00:00
|
|
|
@player.on('ended', ->
|
|
|
|
if CLIENT.leader
|
|
|
|
socket.emit('playNext')
|
|
|
|
)
|
|
|
|
|
|
|
|
@player.on('pause', =>
|
|
|
|
@paused = true
|
|
|
|
if CLIENT.leader
|
|
|
|
sendVideoUpdate()
|
|
|
|
)
|
|
|
|
|
|
|
|
@player.on('play', =>
|
|
|
|
@paused = false
|
|
|
|
if CLIENT.leader
|
|
|
|
sendVideoUpdate()
|
|
|
|
)
|
2015-07-02 06:59:21 +00:00
|
|
|
|
|
|
|
# Workaround for IE-- even after seeking completes, the loading
|
|
|
|
# spinner remains.
|
|
|
|
@player.on('seeked', =>
|
|
|
|
$('.vjs-waiting').removeClass('vjs-waiting')
|
|
|
|
)
|
2015-07-26 19:28:43 +00:00
|
|
|
|
2015-07-26 20:29:06 +00:00
|
|
|
# Workaround for Chrome-- it seems that the click bindings for
|
|
|
|
# the subtitle menu aren't quite set up until after the ready
|
|
|
|
# event finishes, so set a timeout for 1ms to force this code
|
|
|
|
# not to run until the ready() function returns.
|
|
|
|
setTimeout(->
|
|
|
|
$('#ytapiplayer .vjs-subtitles-button .vjs-menu-item').each((i, elem) ->
|
2016-08-12 03:04:51 +00:00
|
|
|
textNode = elem.childNodes[0]
|
|
|
|
if textNode.textContent == localStorage.lastSubtitle
|
2015-07-26 20:29:06 +00:00
|
|
|
elem.click()
|
|
|
|
|
|
|
|
elem.onclick = ->
|
2016-08-12 03:04:51 +00:00
|
|
|
if elem.attributes['aria-checked'].value == 'true'
|
|
|
|
localStorage.lastSubtitle = textNode.textContent
|
2015-07-26 20:29:06 +00:00
|
|
|
)
|
|
|
|
, 1)
|
2015-05-15 05:03:05 +00:00
|
|
|
)
|
|
|
|
)
|
2015-04-24 02:40:08 +00:00
|
|
|
|
|
|
|
load: (data) ->
|
2015-05-15 05:03:05 +00:00
|
|
|
@setMediaProperties(data)
|
2015-07-03 18:24:21 +00:00
|
|
|
# Note: VideoJS does have facilities for loading new videos into the
|
|
|
|
# existing player object, however it appears to be pretty glitchy when
|
|
|
|
# a video can't be played (either previous or next video). It's safer
|
|
|
|
# to just reset the entire thing.
|
2016-03-03 03:31:29 +00:00
|
|
|
@destroy()
|
2015-07-03 18:24:21 +00:00
|
|
|
@loadPlayer(data)
|
2015-05-15 05:03:05 +00:00
|
|
|
|
|
|
|
play: ->
|
|
|
|
@paused = false
|
|
|
|
if @player and @player.readyState() > 0
|
|
|
|
@player.play()
|
|
|
|
|
|
|
|
pause: ->
|
|
|
|
@paused = true
|
|
|
|
if @player and @player.readyState() > 0
|
|
|
|
@player.pause()
|
|
|
|
|
|
|
|
seekTo: (time) ->
|
|
|
|
if @player and @player.readyState() > 0
|
|
|
|
@player.currentTime(time)
|
|
|
|
|
|
|
|
setVolume: (volume) ->
|
2015-07-05 20:50:34 +00:00
|
|
|
if @player
|
2015-05-15 05:03:05 +00:00
|
|
|
@player.volume(volume)
|
|
|
|
|
|
|
|
getTime: (cb) ->
|
|
|
|
if @player and @player.readyState() > 0
|
|
|
|
cb(@player.currentTime())
|
|
|
|
else
|
|
|
|
cb(0)
|
|
|
|
|
|
|
|
getVolume: (cb) ->
|
|
|
|
if @player and @player.readyState() > 0
|
|
|
|
if @player.muted()
|
|
|
|
cb(0)
|
|
|
|
else
|
|
|
|
cb(@player.volume())
|
|
|
|
else
|
|
|
|
cb(VOLUME)
|
2016-03-03 03:31:29 +00:00
|
|
|
|
|
|
|
destroy: ->
|
|
|
|
removeOld()
|
|
|
|
if @player
|
|
|
|
@player.dispose()
|