sortSources = (sources) ->
if not sources
console.error('sortSources() called with null source list')
return []
qualities = ['2160', '1440', '1080', '720', '540', '480', '360', '240']
pref = String(USEROPTS.default_quality)
if USEROPTS.default_quality == 'best'
pref = '2160'
idx = qualities.indexOf(pref)
if idx < 0
idx = 5 # 480p
qualityOrder = qualities.slice(idx).concat(qualities.slice(0, idx).reverse())
qualityOrder.unshift('auto')
sourceOrder = []
flvOrder = []
for quality in qualityOrder
if quality of sources
flv = []
nonflv = []
sources[quality].forEach((source) ->
source.quality = quality
if source.contentType == 'video/flv'
flv.push(source)
else
nonflv.push(source)
)
sourceOrder = sourceOrder.concat(nonflv)
flvOrder = flvOrder.concat(flv)
return sourceOrder.concat(flvOrder).map((source) ->
type: source.contentType
src: source.link
quality: source.quality
)
getSourceLabel = (source) ->
if source.quality is 'auto'
return 'auto'
else
return "#{source.quality}p #{source.type.split('/')[1]}"
waitUntilDefined(window, 'videojs', =>
videojs.options.flash.swf = '/video-js.swf'
)
window.VideoJSPlayer = class VideoJSPlayer extends Player
constructor: (data) ->
if not (this instanceof VideoJSPlayer)
return new VideoJSPlayer(data)
@load(data)
loadPlayer: (data) ->
waitUntilDefined(window, 'videojs', =>
attrs =
width: '100%'
height: '100%'
if @mediaType == 'cm' and data.meta.textTracks
attrs.crossorigin = 'anonymous'
video = $('')
.addClass('video-js vjs-default-skin embed-responsive-item')
.attr(attrs)
removeOld(video)
@sources = sortSources(data.meta.direct)
if @sources.length == 0
console.error('VideoJSPlayer::constructor(): data.meta.direct
has no sources!')
@mediaType = null
return
@sourceIdx = 0
@sources.forEach((source) ->
$('').attr(
src: source.src
type: source.type
res: source.quality
label: getSourceLabel(source)
).appendTo(video)
)
# 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
if data.meta.gdrive_subtitles
data.meta.gdrive_subtitles.available.forEach((subt) ->
label = subt.lang_original
if subt.name
label += " (#{subt.name})"
$('').attr(
src: "/gdvtt/#{data.id}/#{subt.lang}/#{subt.name}.vtt?\
vid=#{data.meta.gdrive_subtitles.vid}"
kind: 'subtitles'
srclang: subt.lang
label: label
).appendTo(video)
)
if data.meta.textTracks
data.meta.textTracks.forEach((track) ->
label = track.name
$('').attr(
src: track.url
kind: 'subtitles'
type: track.type
label: label
).appendTo(video)
)
@player = videojs(video[0],
autoplay: true,
controls: true,
plugins:
videoJsResolutionSwitcher:
default: @sources[0].quality
)
@player.ready(=>
@player.on('error', =>
err = @player.error()
if err and err.code == 4
console.error('Caught error, trying next source')
@sourceIdx++
if @sourceIdx < @sources.length
@player.src(@sources[@sourceIdx])
else
console.error('Out of sources, video will not play')
if @mediaType is 'gd' and not window.hasDriveUserscript
window.promptToInstallDriveUserscript()
)
@setVolume(VOLUME)
@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()
)
# Workaround for IE-- even after seeking completes, the loading
# spinner remains.
@player.on('seeked', =>
$('.vjs-waiting').removeClass('vjs-waiting')
)
# 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) ->
textNode = elem.childNodes[0]
if textNode.textContent == localStorage.lastSubtitle
elem.click()
elem.onclick = ->
if elem.attributes['aria-checked'].value == 'true'
localStorage.lastSubtitle = textNode.textContent
)
, 1)
)
)
load: (data) ->
@setMediaProperties(data)
# 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.
@destroy()
@loadPlayer(data)
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) ->
if @player
@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)
destroy: ->
removeOld()
if @player
@player.dispose()