Bump browserslist from 4.16.3 to 4.16.6
[KisSync.git] / player / videojs.coffee
blob088cf4d263cc330b8501cafdae953baa9aa2f1f3
1 sortSources = (sources) ->
2     if not sources
3         console.error('sortSources() called with null source list')
4         return []
6     qualities = ['2160', '1440', '1080', '720', '540', '480', '360', '240']
7     pref = String(USEROPTS.default_quality)
8     if USEROPTS.default_quality == 'best'
9         pref = '2160'
10     idx = qualities.indexOf(pref)
11     if idx < 0
12         idx = 5 # 480p
14     qualityOrder = qualities.slice(idx).concat(qualities.slice(0, idx).reverse())
15     qualityOrder.unshift('auto')
16     sourceOrder = []
17     flvOrder = []
18     for quality in qualityOrder
19         if quality of sources
20             flv = []
21             nonflv = []
22             sources[quality].forEach((source) ->
23                 source.quality = quality
24                 if source.contentType == 'video/flv'
25                     flv.push(source)
26                 else
27                     nonflv.push(source)
28             )
29             sourceOrder = sourceOrder.concat(nonflv)
30             flvOrder = flvOrder.concat(flv)
32     return sourceOrder.concat(flvOrder).map((source) ->
33         type: source.contentType
34         src: source.link
35         res: source.quality
36         label: getSourceLabel(source)
37     )
39 getSourceLabel = (source) ->
40     if source.res is 'auto'
41         return 'auto'
42     else
43         return "#{source.quality}p #{source.contentType.split('/')[1]}"
45 waitUntilDefined(window, 'videojs', =>
46     videojs.options.flash.swf = '/video-js.swf'
49 window.VideoJSPlayer = class VideoJSPlayer extends Player
50     constructor: (data) ->
51         if not (this instanceof VideoJSPlayer)
52             return new VideoJSPlayer(data)
54         @load(data)
56     loadPlayer: (data) ->
57         waitUntilDefined(window, 'videojs', =>
58             attrs =
59                 width: '100%'
60                 height: '100%'
62             if @mediaType == 'cm' and data.meta.textTracks
63                 attrs.crossorigin = 'anonymous'
65             video = $('<video/>')
66                 .addClass('video-js vjs-default-skin embed-responsive-item')
67                 .attr(attrs)
68             removeOld(video)
70             @sources = sortSources(data.meta.direct)
71             if @sources.length == 0
72                 console.error('VideoJSPlayer::constructor(): data.meta.direct
73                                has no sources!')
74                 @mediaType = null
75                 return
77             @sourceIdx = 0
79             # TODO: Refactor VideoJSPlayer to use a preLoad()/load()/postLoad() pattern
80             # VideoJSPlayer should provide the core functionality and logic for specific
81             # dependent player types (gdrive) should be an extension
82             if data.meta.gdrive_subtitles
83                 data.meta.gdrive_subtitles.available.forEach((subt) ->
84                     label = subt.lang_original
85                     if subt.name
86                         label += " (#{subt.name})"
87                     $('<track/>').attr(
88                         src: "/gdvtt/#{data.id}/#{subt.lang}/#{subt.name}.vtt?\
89                                 vid=#{data.meta.gdrive_subtitles.vid}"
90                         kind: 'subtitles'
91                         srclang: subt.lang
92                         label: label
93                     ).appendTo(video)
94                 )
96             if data.meta.textTracks
97                 data.meta.textTracks.forEach((track) ->
98                     label = track.name
99                     attrs =
100                         src: track.url
101                         kind: 'subtitles'
102                         type: track.type
103                         label: label
105                     if track.default? and track.default
106                         attrs.default = ''
108                     $('<track/>').attr(attrs).appendTo(video)
109                 )
111             @player = videojs(video[0],
112                     # https://github.com/Dash-Industry-Forum/dash.js/issues/2184
113                     autoplay: @sources[0].type != 'application/dash+xml',
114                     controls: true,
115                     plugins:
116                         videoJsResolutionSwitcher:
117                             default: @sources[0].res
118             )
119             @player.ready(=>
120                 # Have to use updateSrc instead of <source> tags
121                 # see: https://github.com/videojs/video.js/issues/3428
122                 @player.updateSrc(@sources)
123                 @player.on('error', =>
124                     err = @player.error()
125                     if err and err.code == 4
126                         console.error('Caught error, trying next source')
127                         # Does this really need to be done manually?
128                         @sourceIdx++
129                         if @sourceIdx < @sources.length
130                             @player.src(@sources[@sourceIdx])
131                         else
132                             console.error('Out of sources, video will not play')
133                             if @mediaType is 'gd' and not window.hasDriveUserscript
134                                 window.promptToInstallDriveUserscript()
135                 )
136                 @setVolume(VOLUME)
137                 @player.on('ended', ->
138                     if CLIENT.leader
139                         socket.emit('playNext')
140                 )
142                 @player.on('pause', =>
143                     @paused = true
144                     if CLIENT.leader
145                         sendVideoUpdate()
146                 )
148                 @player.on('play', =>
149                     @paused = false
150                     if CLIENT.leader
151                         sendVideoUpdate()
152                 )
154                 # Workaround for IE-- even after seeking completes, the loading
155                 # spinner remains.
156                 @player.on('seeked', =>
157                     $('.vjs-waiting').removeClass('vjs-waiting')
158                 )
160                 # Workaround for Chrome-- it seems that the click bindings for
161                 # the subtitle menu aren't quite set up until after the ready
162                 # event finishes, so set a timeout for 1ms to force this code
163                 # not to run until the ready() function returns.
164                 setTimeout(->
165                     $('#ytapiplayer .vjs-subtitles-button .vjs-menu-item').each((i, elem) ->
166                         textNode = elem.childNodes[0]
167                         if textNode.textContent == localStorage.lastSubtitle
168                             elem.click()
170                         elem.onclick = ->
171                             if elem.attributes['aria-checked'].value == 'true'
172                                 localStorage.lastSubtitle = textNode.textContent
173                     )
174                 , 1)
175             )
176         )
178     load: (data) ->
179         @setMediaProperties(data)
180         # Note: VideoJS does have facilities for loading new videos into the
181         # existing player object, however it appears to be pretty glitchy when
182         # a video can't be played (either previous or next video).  It's safer
183         # to just reset the entire thing.
184         @destroy()
185         @loadPlayer(data)
187     play: ->
188         @paused = false
189         if @player and @player.readyState() > 0
190             @player.play()
192     pause: ->
193         @paused = true
194         if @player and @player.readyState() > 0
195             @player.pause()
197     seekTo: (time) ->
198         if @player and @player.readyState() > 0
199             @player.currentTime(time)
201     setVolume: (volume) ->
202         if @player
203             @player.volume(volume)
205     getTime: (cb) ->
206         if @player and @player.readyState() > 0
207             cb(@player.currentTime())
208         else
209             cb(0)
211     getVolume: (cb) ->
212         if @player and @player.readyState() > 0
213             if @player.muted()
214                 cb(0)
215             else
216                 cb(@player.volume())
217         else
218             cb(VOLUME)
220     destroy: ->
221         removeOld()
222         if @player
223             @player.dispose()