Initial decaf commit. Using bare pre-processed player.js instead of
decaffeinate.js
This commit is contained in:
parent
32dddab258
commit
f3306f2263
|
|
@ -1,41 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var coffee = require('coffeescript');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var order = [
|
||||
'base.coffee',
|
||||
'vimeo.coffee',
|
||||
'youtube.coffee',
|
||||
'dailymotion.coffee',
|
||||
'videojs.coffee',
|
||||
'playerjs.coffee',
|
||||
'streamable.coffee',
|
||||
'gdrive-player.coffee',
|
||||
'raw-file.coffee',
|
||||
'soundcloud.coffee',
|
||||
'embed.coffee',
|
||||
'twitch.coffee',
|
||||
'livestream.com.coffee',
|
||||
'custom-embed.coffee',
|
||||
'rtmp.coffee',
|
||||
'smashcast.coffee',
|
||||
'ustream.coffee',
|
||||
'imgur.coffee',
|
||||
'hls.coffee',
|
||||
'twitchclip.coffee',
|
||||
'update.coffee'
|
||||
];
|
||||
|
||||
var buffer = '';
|
||||
order.forEach(function (file) {
|
||||
buffer += fs.readFileSync(
|
||||
path.join(__dirname, '..', 'player', file)
|
||||
) + '\n';
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, '..', 'www', 'js', 'player.js'),
|
||||
coffee.compile(buffer)
|
||||
);
|
||||
7091
package-lock.json
generated
7091
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -41,7 +41,6 @@
|
|||
"yamljs": "^0.2.8"
|
||||
},
|
||||
"scripts": {
|
||||
"build-player": "./bin/build-player.js",
|
||||
"build-server": "babel -D --source-maps --out-dir lib/ src/",
|
||||
"flow": "flow",
|
||||
"lint": "eslint src",
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
window.Player = class Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof Player)
|
||||
return new Player(data)
|
||||
|
||||
@setMediaProperties(data)
|
||||
@paused = false
|
||||
@latched = true
|
||||
@seeklatch = false #used to lock sync latch when seeking for sync
|
||||
@lastSTime = 0
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
|
||||
setMediaProperties: (data) ->
|
||||
@mediaId = data.id
|
||||
@mediaType = data.type
|
||||
@mediaLength = data.seconds
|
||||
|
||||
play: ->
|
||||
@paused = false
|
||||
|
||||
pause: ->
|
||||
@paused = true
|
||||
latch: ->
|
||||
if not @latched
|
||||
@latched = true
|
||||
unlatch: ->
|
||||
if not @seeklatch
|
||||
if @latched
|
||||
$("#latchvid").show()
|
||||
@latched = false
|
||||
else
|
||||
@seeklatch = false
|
||||
latchseek: ->
|
||||
@seeklatch = true
|
||||
getLatch: (cb) ->
|
||||
cb(@latched)
|
||||
seekTo: (time) ->
|
||||
|
||||
setVolume: (volume) ->
|
||||
|
||||
getTime: (cb) ->
|
||||
cb(0)
|
||||
|
||||
isPaused: (cb) ->
|
||||
cb(@paused)
|
||||
|
||||
getVolume: (cb) ->
|
||||
cb(VOLUME)
|
||||
|
||||
getRes: (cb) ->
|
||||
cb();
|
||||
|
||||
destroy: ->
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
CUSTOM_EMBED_WARNING = 'This channel is embedding custom content from %link%.
|
||||
Since this content is not trusted, you must click "Embed" below to allow
|
||||
the content to be embedded.<hr>'
|
||||
|
||||
window.CustomEmbedPlayer = class CustomEmbedPlayer extends EmbedPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof CustomEmbedPlayer)
|
||||
return new CustomEmbedPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
if not data.meta.embed?
|
||||
console.error('CustomEmbedPlayer::load(): missing meta.embed')
|
||||
return
|
||||
|
||||
embedSrc = data.meta.embed.src
|
||||
link = "<a href=\"#{embedSrc}\" target=\"_blank\"><strong>#{embedSrc}</strong></a>"
|
||||
alert = makeAlert('Untrusted Content', CUSTOM_EMBED_WARNING.replace('%link%', link),
|
||||
'alert-warning')
|
||||
.removeClass('col-md-12')
|
||||
$('<button/>').addClass('btn btn-default')
|
||||
.text('Embed')
|
||||
.click(=>
|
||||
super(data)
|
||||
)
|
||||
.appendTo(alert.find('.alert'))
|
||||
removeOld(alert)
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
window.DailymotionPlayer = class DailymotionPlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof DailymotionPlayer)
|
||||
return new DailymotionPlayer(data)
|
||||
|
||||
@setMediaProperties(data)
|
||||
@initialVolumeSet = false
|
||||
@playbackReadyCb = null
|
||||
@latched = true
|
||||
|
||||
waitUntilDefined(window, 'DM', =>
|
||||
removeOld()
|
||||
|
||||
params =
|
||||
autoplay: 1
|
||||
wmode: if USEROPTS.wmode_transparent then 'transparent' else 'opaque'
|
||||
logo: 0
|
||||
|
||||
quality = @mapQuality(USEROPTS.default_quality)
|
||||
if quality != 'auto'
|
||||
params.quality = quality
|
||||
|
||||
@dm = DM.player('ytapiplayer',
|
||||
video: data.id
|
||||
width: parseInt(VWIDTH, 10)
|
||||
height: parseInt(VHEIGHT, 10)
|
||||
params: params
|
||||
)
|
||||
|
||||
@dm.addEventListener('apiready', =>
|
||||
@dmReady = true
|
||||
@dm.addEventListener('ended', ->
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
)
|
||||
|
||||
@dm.addEventListener('pause', =>
|
||||
@paused = true
|
||||
setMini();
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
else
|
||||
@unlatch()
|
||||
)
|
||||
|
||||
@dm.addEventListener('playing', =>
|
||||
@paused = false
|
||||
setMini();
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
|
||||
if not @initialVolumeSet
|
||||
@setVolume(VOLUME)
|
||||
@initialVolumeSet = true
|
||||
)
|
||||
|
||||
@dm.addEventListener('seeked', =>
|
||||
if not CLIENT.leader
|
||||
@unlatch()
|
||||
)
|
||||
|
||||
@dm.addEventListener('timeupdate', =>
|
||||
setDur();
|
||||
)
|
||||
|
||||
@dm.addEventListener('volumechange', =>
|
||||
setMini();
|
||||
)
|
||||
|
||||
# Once the video stops, the internal state of the player
|
||||
# becomes unusable and attempting to load() will corrupt it and
|
||||
# crash the player with an error. As a short–medium term
|
||||
# workaround, mark the player as "not ready" until the next
|
||||
# playback_ready event
|
||||
@dm.addEventListener('video_end', =>
|
||||
@dmReady = false
|
||||
)
|
||||
@dm.addEventListener('playback_ready', =>
|
||||
@dmReady = true
|
||||
handleWindowResize()
|
||||
|
||||
if @playbackReadyCb
|
||||
@playbackReadyCb()
|
||||
@playbackReadyCb = null
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
if @dm and @dmReady
|
||||
@dm.load(data.id)
|
||||
@dm.seek(data.currentTime)
|
||||
|
||||
else if @dm
|
||||
# TODO: Player::load() needs to be made asynchronous in the future
|
||||
console.log('Warning: load() called before DM is ready, queueing callback')
|
||||
@playbackReadyCb = () =>
|
||||
handleWindowResize()
|
||||
|
||||
@dm.load(data.id)
|
||||
@dm.seek(data.currentTime)
|
||||
else
|
||||
console.error('WTF? DailymotionPlayer::load() called but @dm is undefined')
|
||||
|
||||
pause: ->
|
||||
if @dm and @dmReady
|
||||
@paused = true
|
||||
@dm.pause()
|
||||
|
||||
play: ->
|
||||
if @dm and @dmReady
|
||||
@paused = false
|
||||
@dm.play()
|
||||
|
||||
seekTo: (time) ->
|
||||
if @dm and @dmReady
|
||||
@dm.seek(time)
|
||||
|
||||
setVolume: (volume) ->
|
||||
if @dm and @dmReady
|
||||
@dm.setVolume(volume)
|
||||
|
||||
getTime: (cb) ->
|
||||
if @dm and @dmReady
|
||||
cb(@dm.currentTime)
|
||||
else
|
||||
cb(0)
|
||||
|
||||
getVolume: (cb) ->
|
||||
if @dm and @dmReady
|
||||
if @dm.muted
|
||||
cb(0)
|
||||
else
|
||||
volume = @dm.volume
|
||||
# There was once a bug in Dailymotion where it sometimes gave back
|
||||
# volumes in the wrong range. Not sure if this is still a necessary
|
||||
# check.
|
||||
if volume > 1
|
||||
volume /= 100
|
||||
cb(volume)
|
||||
else
|
||||
cb(VOLUME)
|
||||
|
||||
getRes: (cb) ->
|
||||
if @dm and @dmReady
|
||||
cb([@dm.width, @dm.height])
|
||||
else
|
||||
cb()
|
||||
mapQuality: (quality) ->
|
||||
switch String(quality)
|
||||
when '240', '480', '720', '1080' then String(quality)
|
||||
when '360' then '380'
|
||||
when 'best' then '1080'
|
||||
else 'auto'
|
||||
|
||||
destroy: ->
|
||||
if @dm
|
||||
@dm.destroy('ytapiplayer')
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
DEFAULT_ERROR = 'You are currently connected via HTTPS but the embedded content
|
||||
uses non-secure plain HTTP. Your browser therefore blocks it from
|
||||
loading due to mixed content policy. To fix this, embed the video using a
|
||||
secure link if available (https://...), or find another source for the content.'
|
||||
|
||||
genParam = (name, value) ->
|
||||
$('<param/>').attr(
|
||||
name: name
|
||||
value: value
|
||||
)
|
||||
|
||||
window.EmbedPlayer = class EmbedPlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof EmbedPlayer)
|
||||
return new EmbedPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
|
||||
embed = data.meta.embed
|
||||
if not embed?
|
||||
console.error('EmbedPlayer::load(): missing meta.embed')
|
||||
return
|
||||
|
||||
@player = @loadIframe(embed)
|
||||
|
||||
removeOld(@player)
|
||||
|
||||
loadIframe: (embed) ->
|
||||
if embed.src.indexOf('http:') == 0 and location.protocol == 'https:'
|
||||
if @__proto__.mixedContentError?
|
||||
error = @__proto__.mixedContentError
|
||||
else
|
||||
error = DEFAULT_ERROR
|
||||
alert = makeAlert('Mixed Content Error', error, 'alert-danger')
|
||||
.removeClass('col-md-12')
|
||||
alert.find('.close').remove()
|
||||
return alert
|
||||
else
|
||||
iframe = $('<iframe/>').attr(
|
||||
src: embed.src
|
||||
frameborder: '0'
|
||||
allow: 'autoplay'
|
||||
allowfullscreen: '1'
|
||||
)
|
||||
|
||||
return iframe
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
window.GoogleDrivePlayer = class GoogleDrivePlayer extends VideoJSPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof GoogleDrivePlayer)
|
||||
return new GoogleDrivePlayer(data)
|
||||
|
||||
super(data)
|
||||
|
||||
load: (data) ->
|
||||
if not window.hasDriveUserscript
|
||||
window.promptToInstallDriveUserscript()
|
||||
else if window.hasDriveUserscript
|
||||
window.maybePromptToUpgradeUserscript()
|
||||
if typeof window.getGoogleDriveMetadata is 'function'
|
||||
setTimeout(=>
|
||||
backoffRetry((cb) ->
|
||||
window.getGoogleDriveMetadata(data.id, cb)
|
||||
, (error, metadata) =>
|
||||
if error
|
||||
console.error(error)
|
||||
alertBox = window.document.createElement('div')
|
||||
alertBox.className = 'alert alert-danger'
|
||||
alertBox.textContent = error
|
||||
document.getElementById('ytapiplayer').appendChild(alertBox)
|
||||
else
|
||||
data.meta.direct = metadata.videoMap
|
||||
super(data)
|
||||
, {
|
||||
maxTries: 3
|
||||
delay: 1000
|
||||
factor: 1.2
|
||||
jitter: 500
|
||||
})
|
||||
, Math.random() * 1000)
|
||||
|
||||
window.promptToInstallDriveUserscript = ->
|
||||
if document.getElementById('prompt-install-drive-userscript')
|
||||
return
|
||||
alertBox = document.createElement('div')
|
||||
alertBox.id = 'prompt-install-drive-userscript'
|
||||
alertBox.className = 'alert alert-info'
|
||||
alertBox.innerHTML = """
|
||||
Due to continual breaking changes making it increasingly difficult to
|
||||
maintain Google Drive support, Google Drive now requires installing
|
||||
a userscript in order to play the video."""
|
||||
alertBox.appendChild(document.createElement('br'))
|
||||
infoLink = document.createElement('a')
|
||||
infoLink.className = 'btn btn-info'
|
||||
infoLink.href = '/google_drive_userscript'
|
||||
infoLink.textContent = 'Click here for details'
|
||||
infoLink.target = '_blank'
|
||||
alertBox.appendChild(infoLink)
|
||||
|
||||
closeButton = document.createElement('button')
|
||||
closeButton.className = 'close pull-right'
|
||||
closeButton.innerHTML = '×'
|
||||
closeButton.onclick = ->
|
||||
alertBox.parentNode.removeChild(alertBox)
|
||||
alertBox.insertBefore(closeButton, alertBox.firstChild)
|
||||
removeOld($('<div/>').append(alertBox))
|
||||
|
||||
window.tellUserNotToContactMeAboutThingsThatAreNotSupported = ->
|
||||
if document.getElementById('prompt-no-gdrive-support')
|
||||
return
|
||||
alertBox = document.createElement('div')
|
||||
alertBox.id = 'prompt-no-gdrive-support'
|
||||
alertBox.className = 'alert alert-danger'
|
||||
alertBox.innerHTML = """
|
||||
CyTube has detected an error in Google Drive playback. Please note that the
|
||||
staff in CyTube support channels DO NOT PROVIDE SUPPORT FOR GOOGLE DRIVE. It
|
||||
is left in the code as-is for existing users, but we will not assist in
|
||||
troubleshooting any errors that occur.<br>"""
|
||||
alertBox.appendChild(document.createElement('br'))
|
||||
infoLink = document.createElement('a')
|
||||
infoLink.className = 'btn btn-danger'
|
||||
infoLink.href = 'https://github.com/calzoneman/sync/wiki/Frequently-Asked-Questions#why-dont-you-support-google-drive-anymore'
|
||||
infoLink.textContent = 'Click here for details'
|
||||
infoLink.target = '_blank'
|
||||
alertBox.appendChild(infoLink)
|
||||
|
||||
closeButton = document.createElement('button')
|
||||
closeButton.className = 'close pull-right'
|
||||
closeButton.innerHTML = '×'
|
||||
closeButton.onclick = ->
|
||||
alertBox.parentNode.removeChild(alertBox)
|
||||
alertBox.insertBefore(closeButton, alertBox.firstChild)
|
||||
removeOld($('<div/>').append(alertBox))
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
window.HLSPlayer = class HLSPlayer extends VideoJSPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof HLSPlayer)
|
||||
return new HLSPlayer(data)
|
||||
|
||||
@setupMeta(data)
|
||||
super(data)
|
||||
|
||||
load: (data) ->
|
||||
@setupMeta(data)
|
||||
super(data)
|
||||
|
||||
setupMeta: (data) ->
|
||||
data.meta.direct =
|
||||
# Quality is required for data.meta.direct processing but doesn't
|
||||
# matter here because it's dictated by the stream. Arbitrarily
|
||||
# choose 480.
|
||||
480: [
|
||||
{
|
||||
link: data.id
|
||||
contentType: 'application/x-mpegURL'
|
||||
}
|
||||
]
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
window.ImgurPlayer = class ImgurPlayer extends EmbedPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof ImgurPlayer)
|
||||
return new ImgurPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
data.meta.embed =
|
||||
tag: 'iframe'
|
||||
src: "https://imgur.com/a/#{data.id}/embed"
|
||||
super(data)
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
window.LivestreamPlayer = class LivestreamPlayer extends EmbedPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof LivestreamPlayer)
|
||||
return new LivestreamPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
data.meta.embed =
|
||||
src: "https://cdn.livestream.com/embed/#{data.id}?\
|
||||
layout=4&\
|
||||
color=0x000000&\
|
||||
iconColorOver=0xe7e7e7&\
|
||||
iconColor=0xcccccc"
|
||||
tag: 'iframe'
|
||||
super(data)
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
window.PlayerJSPlayer = class PlayerJSPlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof PlayerJSPlayer)
|
||||
return new PlayerJSPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
@ready = false
|
||||
@finishing = false
|
||||
|
||||
if not data.meta.playerjs
|
||||
throw new Error('Invalid input: missing meta.playerjs')
|
||||
|
||||
waitUntilDefined(window, 'playerjs', =>
|
||||
iframe = $('<iframe/>')
|
||||
.attr(src: data.meta.playerjs.src)
|
||||
|
||||
removeOld(iframe)
|
||||
|
||||
@player = new playerjs.Player(iframe[0])
|
||||
@player.on('ready', =>
|
||||
@player.on('error', (error) =>
|
||||
console.error('PlayerJS error', error.stack)
|
||||
)
|
||||
@player.on('ended', ->
|
||||
# Streamable seems to not implement this since it loops
|
||||
# gotta use the timeupdate hack below
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
)
|
||||
@player.on('timeupdate', (time) =>
|
||||
if time.duration - time.seconds < 1 and not @finishing
|
||||
setTimeout(=>
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
@pause()
|
||||
, (time.duration - time.seconds) * 1000)
|
||||
@finishing = true
|
||||
)
|
||||
@player.on('play', ->
|
||||
@paused = false
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
@player.on('pause', ->
|
||||
@paused = true
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
|
||||
@player.setVolume(VOLUME * 100)
|
||||
|
||||
if not @paused
|
||||
@player.play()
|
||||
|
||||
@ready = true
|
||||
)
|
||||
)
|
||||
|
||||
play: ->
|
||||
@paused = false
|
||||
if @player and @ready
|
||||
@player.play()
|
||||
|
||||
pause: ->
|
||||
@paused = true
|
||||
if @player and @ready
|
||||
@player.pause()
|
||||
|
||||
seekTo: (time) ->
|
||||
if @player and @ready
|
||||
@player.setCurrentTime(time)
|
||||
|
||||
setVolume: (volume) ->
|
||||
if @player and @ready
|
||||
@player.setVolume(volume * 100)
|
||||
|
||||
getTime: (cb) ->
|
||||
if @player and @ready
|
||||
@player.getCurrentTime(cb)
|
||||
else
|
||||
cb(0)
|
||||
|
||||
getVolume: (cb) ->
|
||||
if @player and @ready
|
||||
@player.getVolume((volume) ->
|
||||
cb(volume / 100)
|
||||
)
|
||||
else
|
||||
cb(VOLUME)
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
codecToMimeType = (codec) ->
|
||||
switch codec
|
||||
when 'mov/h264', 'mov/av1' then 'video/mp4'
|
||||
when 'flv/h264' then 'video/flv'
|
||||
when 'matroska/vp8', 'matroska/vp9', 'matroska/av1' then 'video/webm'
|
||||
when 'ogg/theora' then 'video/ogg'
|
||||
when 'mp3' then 'audio/mp3'
|
||||
when 'vorbis' then 'audio/ogg'
|
||||
when 'aac' then 'audio/aac'
|
||||
when 'opus' then 'audio/opus'
|
||||
else 'video/flv'
|
||||
|
||||
window.FilePlayer = class FilePlayer extends VideoJSPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof FilePlayer)
|
||||
return new FilePlayer(data)
|
||||
|
||||
data.meta.direct =
|
||||
480: [{
|
||||
contentType: codecToMimeType(data.meta.codec)
|
||||
link: data.id
|
||||
}]
|
||||
super(data)
|
||||
|
||||
load: (data) ->
|
||||
data.meta.direct =
|
||||
480: [{
|
||||
contentType: codecToMimeType(data.meta.codec)
|
||||
link: data.id
|
||||
}]
|
||||
super(data)
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
window.RTMPPlayer = class RTMPPlayer extends VideoJSPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof RTMPPlayer)
|
||||
return new RTMPPlayer(data)
|
||||
|
||||
@setupMeta(data)
|
||||
super(data)
|
||||
|
||||
load: (data) ->
|
||||
@setupMeta(data)
|
||||
super(data)
|
||||
|
||||
setupMeta: (data) ->
|
||||
data.meta.direct =
|
||||
# Quality is required for data.meta.direct processing but doesn't
|
||||
# matter here because it's dictated by the stream. Arbitrarily
|
||||
# choose 480.
|
||||
480: [
|
||||
{
|
||||
link: data.id
|
||||
contentType: 'rtmp/flv'
|
||||
}
|
||||
]
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
window.SmashcastPlayer = class SmashcastPlayer extends EmbedPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof SmashcastPlayer)
|
||||
return new SmashcastPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
data.meta.embed =
|
||||
src: "https://www.smashcast.tv/embed/#{data.id}"
|
||||
tag: 'iframe'
|
||||
super(data)
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
window.SoundCloudPlayer = class SoundCloudPlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof SoundCloudPlayer)
|
||||
return new SoundCloudPlayer(data)
|
||||
|
||||
@setMediaProperties(data)
|
||||
|
||||
waitUntilDefined(window, 'SC', =>
|
||||
removeOld()
|
||||
|
||||
# For tracks that are private, but embeddable, the API returns a
|
||||
# special URL to load into the player.
|
||||
# TODO: rename scuri?
|
||||
if data.meta.scuri
|
||||
soundUrl = data.meta.scuri
|
||||
else
|
||||
soundUrl = data.id
|
||||
|
||||
widget = $('<iframe/>').appendTo($('#ytapiplayer'))
|
||||
widget.attr(
|
||||
id: 'scplayer'
|
||||
src: "https://w.soundcloud.com/player/?url=#{soundUrl}"
|
||||
)
|
||||
|
||||
# Soundcloud embed widget doesn't have a volume control.
|
||||
sliderHolder = $('<div/>').attr('id', 'soundcloud-volume-holder')
|
||||
.insertAfter(widget)
|
||||
$('<span/>').attr('id', 'soundcloud-volume-label')
|
||||
.addClass('label label-default')
|
||||
.text('Volume')
|
||||
.appendTo(sliderHolder)
|
||||
volumeSlider = $('<div/>').attr('id', 'soundcloud-volume')
|
||||
.appendTo(sliderHolder)
|
||||
.slider(
|
||||
range: 'min'
|
||||
value: VOLUME * 100
|
||||
stop: (event, ui) =>
|
||||
@setVolume(ui.value / 100)
|
||||
)
|
||||
|
||||
@soundcloud = SC.Widget(widget[0])
|
||||
@soundcloud.bind(SC.Widget.Events.READY, =>
|
||||
@soundcloud.ready = true
|
||||
@setVolume(VOLUME)
|
||||
@play()
|
||||
|
||||
@soundcloud.bind(SC.Widget.Events.PAUSE, =>
|
||||
@paused = true
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
@soundcloud.bind(SC.Widget.Events.PLAY, =>
|
||||
@paused = false
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
@soundcloud.bind(SC.Widget.Events.FINISH, =>
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
if @soundcloud and @soundcloud.ready
|
||||
if data.meta.scuri
|
||||
soundUrl = data.meta.scuri
|
||||
else
|
||||
soundUrl = data.id
|
||||
@soundcloud.load(soundUrl, auto_play: true)
|
||||
@soundcloud.bind(SC.Widget.Events.READY, =>
|
||||
@setVolume(VOLUME)
|
||||
)
|
||||
else
|
||||
console.error('SoundCloudPlayer::load() called but soundcloud is not ready')
|
||||
|
||||
play: ->
|
||||
@paused = false
|
||||
if @soundcloud and @soundcloud.ready
|
||||
@soundcloud.play()
|
||||
|
||||
pause: ->
|
||||
@paused = true
|
||||
if @soundcloud and @soundcloud.ready
|
||||
@soundcloud.pause()
|
||||
|
||||
seekTo: (time) ->
|
||||
if @soundcloud and @soundcloud.ready
|
||||
# SoundCloud measures time in milliseconds while CyTube uses seconds.
|
||||
@soundcloud.seekTo(time * 1000)
|
||||
|
||||
setVolume: (volume) ->
|
||||
if @soundcloud and @soundcloud.ready
|
||||
@soundcloud.setVolume(volume * 100)
|
||||
|
||||
getTime: (cb) ->
|
||||
if @soundcloud and @soundcloud.ready
|
||||
# Returned time is in milliseconds; CyTube expects seconds
|
||||
@soundcloud.getPosition((time) -> cb(time / 1000))
|
||||
else
|
||||
cb(0)
|
||||
|
||||
getVolume: (cb) ->
|
||||
if @soundcloud and @soundcloud.ready
|
||||
@soundcloud.getVolume((vol) -> cb(vol / 100))
|
||||
else
|
||||
cb(VOLUME)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
window.StreamablePlayer = class StreamablePlayer extends PlayerJSPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof StreamablePlayer)
|
||||
return new StreamablePlayer(data)
|
||||
|
||||
super(data)
|
||||
|
||||
load: (data) ->
|
||||
data.meta.playerjs =
|
||||
src: "https://streamable.com/e/#{data.id}"
|
||||
|
||||
super(data)
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
window.TWITCH_PARAMS_ERROR = 'The Twitch embed player now uses parameters which only
|
||||
work if the following requirements are met: (1) The embedding website uses
|
||||
HTTPS; (2) The embedding website uses the default port (443) and is accessed
|
||||
via https://example.com instead of https://example.com:port. I have no
|
||||
control over this -- see <a href="https://discuss.dev.twitch.tv/t/twitch-embedded-player-migration-timeline-update/25588" rel="noopener noreferrer" target="_blank">this Twitch post</a>
|
||||
for details'
|
||||
|
||||
window.TwitchPlayer = class TwitchPlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof TwitchPlayer)
|
||||
return new TwitchPlayer(data)
|
||||
|
||||
@setMediaProperties(data)
|
||||
waitUntilDefined(window, 'Twitch', =>
|
||||
waitUntilDefined(Twitch, 'Player', =>
|
||||
@init(data)
|
||||
)
|
||||
)
|
||||
|
||||
init: (data) ->
|
||||
removeOld()
|
||||
|
||||
if location.hostname != location.host or location.protocol != 'https:'
|
||||
alert = makeAlert(
|
||||
'Twitch API Parameters',
|
||||
window.TWITCH_PARAMS_ERROR,
|
||||
'alert-danger'
|
||||
).removeClass('col-md-12')
|
||||
removeOld(alert)
|
||||
@twitch = null
|
||||
return
|
||||
|
||||
options =
|
||||
parent: [location.hostname]
|
||||
width: $('#ytapiplayer').width()
|
||||
height: $('#ytapiplayer').height()
|
||||
|
||||
if data.type is 'tv'
|
||||
# VOD
|
||||
options.video = data.id
|
||||
else
|
||||
# Livestream
|
||||
options.channel = data.id
|
||||
|
||||
@twitch = new Twitch.Player('ytapiplayer', options)
|
||||
@twitch.addEventListener(Twitch.Player.READY, =>
|
||||
@setVolume(VOLUME)
|
||||
@twitch.setQuality(@mapQuality(USEROPTS.default_quality))
|
||||
@twitch.addEventListener(Twitch.Player.PLAY, =>
|
||||
@paused = false
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
@twitch.addEventListener(Twitch.Player.PAUSE, =>
|
||||
@paused = true
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
@twitch.addEventListener(Twitch.Player.ENDED, =>
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
)
|
||||
)
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
try
|
||||
if data.type is 'tv'
|
||||
# VOD
|
||||
@twitch.setVideo(data.id)
|
||||
else
|
||||
# Livestream
|
||||
@twitch.setChannel(data.id)
|
||||
catch error
|
||||
console.error(error)
|
||||
|
||||
pause: ->
|
||||
try
|
||||
@twitch.pause()
|
||||
@paused = true
|
||||
catch error
|
||||
console.error(error)
|
||||
|
||||
play: ->
|
||||
try
|
||||
@twitch.play()
|
||||
@paused = false
|
||||
catch error
|
||||
console.error(error)
|
||||
|
||||
seekTo: (time) ->
|
||||
try
|
||||
@twitch.seek(time)
|
||||
catch error
|
||||
console.error(error)
|
||||
|
||||
getTime: (cb) ->
|
||||
try
|
||||
cb(@twitch.getCurrentTime())
|
||||
catch error
|
||||
console.error(error)
|
||||
|
||||
setVolume: (volume) ->
|
||||
try
|
||||
@twitch.setVolume(volume)
|
||||
if volume > 0
|
||||
@twitch.setMuted(false)
|
||||
catch error
|
||||
console.error(error)
|
||||
|
||||
getVolume: (cb) ->
|
||||
try
|
||||
if @twitch.isPaused()
|
||||
cb(0)
|
||||
else
|
||||
cb(@twitch.getVolume())
|
||||
catch error
|
||||
console.error(error)
|
||||
|
||||
mapQuality: (quality) ->
|
||||
switch String(quality)
|
||||
when '1080' then 'chunked'
|
||||
when '720' then 'high'
|
||||
when '480' then 'medium'
|
||||
when '360' then 'low'
|
||||
when '240' then 'mobile'
|
||||
when 'best' then 'chunked'
|
||||
else ''
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
window.TwitchClipPlayer = class TwitchClipPlayer extends EmbedPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof TwitchClipPlayer)
|
||||
return new TwitchClipPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
if location.hostname != location.host or location.protocol != 'https:'
|
||||
alert = makeAlert(
|
||||
'Twitch API Parameters',
|
||||
window.TWITCH_PARAMS_ERROR,
|
||||
'alert-danger'
|
||||
).removeClass('col-md-12')
|
||||
removeOld(alert)
|
||||
return
|
||||
|
||||
data.meta.embed =
|
||||
tag: 'iframe'
|
||||
src: "https://clips.twitch.tv/embed?clip=#{data.id}&parent=#{location.host}"
|
||||
super(data)
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
TYPE_MAP =
|
||||
yt: YouTubePlayer
|
||||
vi: VimeoPlayer
|
||||
dm: DailymotionPlayer
|
||||
gd: GoogleDrivePlayer
|
||||
gp: VideoJSPlayer
|
||||
fi: FilePlayer
|
||||
sc: SoundCloudPlayer
|
||||
li: LivestreamPlayer
|
||||
tw: TwitchPlayer
|
||||
tv: TwitchPlayer
|
||||
cu: CustomEmbedPlayer
|
||||
rt: RTMPPlayer
|
||||
hb: SmashcastPlayer
|
||||
us: UstreamPlayer
|
||||
im: ImgurPlayer
|
||||
hl: HLSPlayer
|
||||
sb: StreamablePlayer
|
||||
tc: TwitchClipPlayer
|
||||
cm: VideoJSPlayer
|
||||
|
||||
window.loadMediaPlayer = (data) ->
|
||||
try
|
||||
if window.PLAYER
|
||||
window.PLAYER.destroy()
|
||||
catch error
|
||||
console.error error
|
||||
|
||||
if data.meta.direct and data.type is 'vi'
|
||||
try
|
||||
window.PLAYER = new VideoJSPlayer(data)
|
||||
catch e
|
||||
console.error e
|
||||
else if data.type of TYPE_MAP
|
||||
try
|
||||
window.PLAYER = TYPE_MAP[data.type](data)
|
||||
catch e
|
||||
console.error e
|
||||
|
||||
window.handleMediaUpdate = (data) ->
|
||||
PLAYER = window.PLAYER
|
||||
|
||||
|
||||
#update airdate
|
||||
dispSTimes();
|
||||
|
||||
PLAYER.lastSTime = data.currentTime;
|
||||
if not PLAYER.latched #check if the shits latched, if not stop while we're ahead.
|
||||
return
|
||||
|
||||
# Do not update if the current time is past the end of the video, unless
|
||||
# the video has length 0 (which is a special case for livestreams)
|
||||
if (typeof PLAYER.mediaLength is 'number' and
|
||||
PLAYER.mediaLength > 0 and
|
||||
data.currentTime > PLAYER.mediaLength)
|
||||
return
|
||||
|
||||
# Negative currentTime indicates a lead-in for clients to load the video,
|
||||
# but not play it yet (helps with initial buffering)
|
||||
waiting = data.currentTime < 0
|
||||
|
||||
# Load a new video in the same player if the ID changed
|
||||
if data.id and data.id != PLAYER.mediaId
|
||||
if data.currentTime < 0
|
||||
data.currentTime = 0
|
||||
PLAYER.load(data)
|
||||
PLAYER.play()
|
||||
|
||||
if waiting
|
||||
PLAYER.seekTo(0)
|
||||
PLAYER.latchseek()
|
||||
# YouTube player has a race condition that crashes the player if
|
||||
# play(), seek(0), and pause() are called quickly without waiting
|
||||
# for events to fire. Setting a flag variable that is checked in the
|
||||
# event handler mitigates this.
|
||||
if PLAYER instanceof YouTubePlayer
|
||||
PLAYER.pauseSeekRaceCondition = true
|
||||
else
|
||||
PLAYER.pause()
|
||||
return
|
||||
else if PLAYER instanceof YouTubePlayer
|
||||
PLAYER.pauseSeekRaceCondition = false
|
||||
|
||||
if CLIENT.leader or not USEROPTS.synch
|
||||
return
|
||||
|
||||
if data.paused and not PLAYER.paused
|
||||
PLAYER.seekTo(data.currentTime)
|
||||
PLAYER.latchseek()
|
||||
PLAYER.pause()
|
||||
else if PLAYER.paused and not data.paused
|
||||
PLAYER.play()
|
||||
|
||||
PLAYER.getTime((seconds) ->
|
||||
time = data.currentTime
|
||||
diff = (time - seconds) or time
|
||||
accuracy = USEROPTS.sync_accuracy
|
||||
|
||||
# Dailymotion can't seek very accurately in Flash due to keyframe
|
||||
# placement. Accuracy should not be set lower than 5 or the video
|
||||
# may be very choppy.
|
||||
if PLAYER instanceof DailymotionPlayer
|
||||
accuracy = Math.max(accuracy, 5)
|
||||
|
||||
if diff > accuracy
|
||||
# The player is behind the correct time
|
||||
PLAYER.seekTo(time)
|
||||
PLAYER.latchseek()
|
||||
else if diff < -accuracy
|
||||
# The player is ahead of the correct time
|
||||
# Don't seek all the way back, to account for possible buffering.
|
||||
# However, do seek all the way back for Dailymotion due to the
|
||||
# keyframe issue mentioned above.
|
||||
if not (PLAYER instanceof DailymotionPlayer)
|
||||
time += 1
|
||||
PLAYER.seekTo(time)
|
||||
PLAYER.latchseek()
|
||||
)
|
||||
|
||||
window.removeOld = (replace) ->
|
||||
$('#soundcloud-volume-holder').remove()
|
||||
replace ?= $('<div/>').addClass('embed-responsive-item')
|
||||
old = $('#ytapiplayer')
|
||||
replace.insertBefore(old)
|
||||
old.remove()
|
||||
replace.attr('id', 'ytapiplayer')
|
||||
return replace
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
window.UstreamPlayer = class UstreamPlayer extends EmbedPlayer
|
||||
constructor: (data) ->
|
||||
if not (this instanceof UstreamPlayer)
|
||||
return new UstreamPlayer(data)
|
||||
|
||||
@load(data)
|
||||
|
||||
load: (data) ->
|
||||
data.meta.embed =
|
||||
tag: 'iframe'
|
||||
src: "https://www.ustream.tv/embed/#{data.id}?html5ui"
|
||||
super(data)
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
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
|
||||
res: source.quality
|
||||
label: getSourceLabel(source)
|
||||
)
|
||||
|
||||
getSourceLabel = (source) ->
|
||||
if source.res is 'auto'
|
||||
return 'auto'
|
||||
else
|
||||
return "#{source.quality}p #{source.contentType.split('/')[1]}"
|
||||
|
||||
waitUntilDefined(window, 'videojs', =>
|
||||
videojs.options.flash.swf = '/video-js.swf'
|
||||
)
|
||||
|
||||
hasAnyTextTracks = (data) ->
|
||||
ntracks = data?.meta?.textTracks?.length ? 0
|
||||
return ntracks > 0
|
||||
|
||||
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 hasAnyTextTracks(data)
|
||||
attrs.crossorigin = 'anonymous'
|
||||
|
||||
video = $('<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
|
||||
|
||||
# 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})"
|
||||
$('<track/>').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)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@player = videojs(video[0],
|
||||
# https://github.com/Dash-Industry-Forum/dash.js/issues/2184
|
||||
autoplay: @sources[0].type != 'application/dash+xml',
|
||||
controls: true,
|
||||
plugins:
|
||||
videoJsResolutionSwitcher:
|
||||
default: @sources[0].res
|
||||
)
|
||||
@player.ready(=>
|
||||
# Have to use updateSrc instead of <source> tags
|
||||
# see: https://github.com/videojs/video.js/issues/3428
|
||||
@player.updateSrc(@sources)
|
||||
@player.on('error', =>
|
||||
err = @player.error()
|
||||
if err and err.code == 4
|
||||
console.error('Caught error, trying next source')
|
||||
# Does this really need to be done manually?
|
||||
@sourceIdx++
|
||||
if @sourceIdx < @sources.length
|
||||
@player.src(@sources[@sourceIdx])
|
||||
else
|
||||
console.error('Out of sources, video will not play')
|
||||
if @mediaType is 'gd'
|
||||
if not window.hasDriveUserscript
|
||||
window.promptToInstallDriveUserscript()
|
||||
else
|
||||
window.tellUserNotToContactMeAboutThingsThatAreNotSupported()
|
||||
)
|
||||
@setVolume(VOLUME)
|
||||
@player.on('ended', ->
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
)
|
||||
|
||||
@player.on('pause', =>
|
||||
@paused = true
|
||||
setMini();
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
else
|
||||
@unlatch()
|
||||
|
||||
)
|
||||
|
||||
@player.on('play', =>
|
||||
@paused = false
|
||||
setMini();
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
|
||||
@player.on('volumechange', =>
|
||||
setMini();
|
||||
)
|
||||
|
||||
# Workaround for IE-- even after seeking completes, the loading
|
||||
# spinner remains.
|
||||
@player.on('seeked', =>
|
||||
$('.vjs-waiting').removeClass('vjs-waiting')
|
||||
if not CLIENT.leader #this part has nothing to do with IE and all to do with sync latching :P
|
||||
@unlatch()
|
||||
|
||||
)
|
||||
|
||||
|
||||
@player.on('canplay', =>
|
||||
handleWindowResize()
|
||||
console.log(@player.children)
|
||||
)
|
||||
|
||||
@player.on('timeupdate', =>
|
||||
setDur();
|
||||
)
|
||||
|
||||
# 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)
|
||||
@latched = true
|
||||
# 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)
|
||||
@setTracks(data)
|
||||
|
||||
setTracks: (data) ->
|
||||
if data.meta.textTracks
|
||||
data.meta.textTracks.forEach((track) ->
|
||||
label = track.name
|
||||
$('<track>').attr(
|
||||
src: track.url
|
||||
kind: 'subtitles'
|
||||
type: track.type
|
||||
label: label
|
||||
default: true
|
||||
).prependTo("video")
|
||||
)
|
||||
|
||||
play: ->
|
||||
@paused = false
|
||||
if @player and @player.readyState() > 0
|
||||
@player.play()
|
||||
|
||||
pause: ->
|
||||
@paused = true
|
||||
if @player and @player.readyState() > 0
|
||||
@player.pause()
|
||||
if not CLIENT.leader
|
||||
@unlatch()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
getRes: (cb) ->
|
||||
if @player
|
||||
cb([@player.videoWidth(), @player.videoHeight()])
|
||||
else
|
||||
cb()
|
||||
|
||||
destroy: ->
|
||||
removeOld()
|
||||
if @player
|
||||
@player.dispose()
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
window.VimeoPlayer = class VimeoPlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof VimeoPlayer)
|
||||
return new VimeoPlayer(data)
|
||||
|
||||
@load(data)
|
||||
@resx = 0
|
||||
@resy = 0
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
@latched = true
|
||||
|
||||
waitUntilDefined(window, 'Vimeo', =>
|
||||
video = $('<iframe/>')
|
||||
removeOld(video)
|
||||
video.attr(
|
||||
src: "https://player.vimeo.com/video/#{data.id}"
|
||||
webkitallowfullscreen: true
|
||||
mozallowfullscreen: true
|
||||
allowfullscreen: true
|
||||
)
|
||||
|
||||
if USEROPTS.wmode_transparent
|
||||
video.attr('wmode', 'transparent')
|
||||
|
||||
@vimeo = new Vimeo.Player(video[0])
|
||||
|
||||
@vimeo.on('ended', =>
|
||||
if CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
)
|
||||
|
||||
@vimeo.on('pause', =>
|
||||
@paused = true
|
||||
setMini();
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
else
|
||||
@unlatch()
|
||||
|
||||
)
|
||||
|
||||
@vimeo.on('play', =>
|
||||
@paused = false
|
||||
setMini();
|
||||
handleWindowResize()
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
)
|
||||
|
||||
@vimeo.on('seeked', =>
|
||||
if not CLIENT.leader
|
||||
@unlatch()
|
||||
)
|
||||
|
||||
@vimeo.on('timeupdate', =>
|
||||
setDur();
|
||||
)
|
||||
|
||||
@vimeo.on('volumechange', =>
|
||||
setMini();
|
||||
)
|
||||
|
||||
|
||||
@play()
|
||||
@setVolume(VOLUME)
|
||||
|
||||
@vimeo.getVideoWidth().then((wid) ->
|
||||
window.PLAYER.resx = wid
|
||||
).catch((error) ->
|
||||
console.error('vimeo::getVideoWidth():', error)
|
||||
)
|
||||
|
||||
@vimeo.getVideoHeight().then((hgt) ->
|
||||
window.PLAYER.resy = hgt
|
||||
).catch((error) ->
|
||||
console.error('vimeo::getVideoHeight():', error)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
play: ->
|
||||
@paused = false
|
||||
if @vimeo
|
||||
@vimeo.play().catch((error) ->
|
||||
console.error('vimeo::play():', error)
|
||||
)
|
||||
|
||||
pause: ->
|
||||
@paused = true
|
||||
if @vimeo
|
||||
@vimeo.pause().catch((error) ->
|
||||
console.error('vimeo::pause():', error)
|
||||
)
|
||||
|
||||
seekTo: (time) ->
|
||||
if @vimeo
|
||||
@vimeo.setCurrentTime(time).catch((error) ->
|
||||
console.error('vimeo::setCurrentTime():', error)
|
||||
)
|
||||
|
||||
setVolume: (volume) ->
|
||||
if @vimeo
|
||||
@vimeo.setVolume(volume).catch((error) ->
|
||||
console.error('vimeo::setVolume():', error)
|
||||
)
|
||||
|
||||
getTime: (cb) ->
|
||||
if @vimeo
|
||||
@vimeo.getCurrentTime().then((time) ->
|
||||
cb(parseFloat(time))
|
||||
).catch((error) ->
|
||||
console.error('vimeo::getCurrentTime():', error)
|
||||
)
|
||||
else
|
||||
cb(0)
|
||||
|
||||
getVolume: (cb) ->
|
||||
if @vimeo
|
||||
@vimeo.getVolume().then((volume) ->
|
||||
cb(parseFloat(volume))
|
||||
).catch((error) ->
|
||||
console.error('vimeo::getVolume():', error)
|
||||
)
|
||||
else
|
||||
cb(VOLUME)
|
||||
|
||||
getRes: (cb) ->
|
||||
if @vimeo
|
||||
cb([@resx,@resy])
|
||||
else
|
||||
cb()
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
window.YouTubePlayer = class YouTubePlayer extends Player
|
||||
constructor: (data) ->
|
||||
if not (this instanceof YouTubePlayer)
|
||||
return new YouTubePlayer(data)
|
||||
|
||||
@setMediaProperties(data)
|
||||
@pauseSeekRaceCondition = false
|
||||
|
||||
waitUntilDefined(window, 'YT', =>
|
||||
# Even after window.YT is defined, YT.Player may not be, which causes a
|
||||
# 'YT.Player is not a constructor' error occasionally
|
||||
waitUntilDefined(YT, 'Player', =>
|
||||
removeOld()
|
||||
|
||||
wmode = if USEROPTS.wmode_transparent then 'transparent' else 'opaque'
|
||||
@yt = new YT.Player('ytapiplayer',
|
||||
videoId: data.id
|
||||
playerVars:
|
||||
autohide: 1
|
||||
autoplay: 1
|
||||
controls: 1
|
||||
iv_load_policy: 3 # iv_load_policy 3 indicates no annotations
|
||||
rel: 0
|
||||
wmode: wmode
|
||||
events:
|
||||
onReady: @onReady.bind(this)
|
||||
onStateChange: @onStateChange.bind(this)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
load: (data) ->
|
||||
@setMediaProperties(data)
|
||||
if @yt and @yt.ready
|
||||
@yt.loadVideoById(data.id, data.currentTime)
|
||||
else
|
||||
console.error('WTF? YouTubePlayer::load() called but yt is not ready')
|
||||
|
||||
onReady: ->
|
||||
@yt.ready = true
|
||||
@setVolume(VOLUME)
|
||||
|
||||
onStateChange: (ev) ->
|
||||
# If you pause the video before the first PLAYING
|
||||
# event is emitted, weird things happen (or at least that was true
|
||||
# whenever this comment was authored in 2015).
|
||||
if ev.data == YT.PlayerState.PLAYING and @pauseSeekRaceCondition
|
||||
@pause()
|
||||
@pauseSeekRaceCondition = false
|
||||
|
||||
if (ev.data == YT.PlayerState.PAUSED and not @paused) or
|
||||
(ev.data == YT.PlayerState.PLAYING and @paused)
|
||||
@paused = (ev.data == YT.PlayerState.PAUSED)
|
||||
if CLIENT.leader
|
||||
sendVideoUpdate()
|
||||
|
||||
if ev.data == YT.PlayerState.ENDED and CLIENT.leader
|
||||
socket.emit('playNext')
|
||||
|
||||
play: ->
|
||||
@paused = false
|
||||
if @yt and @yt.ready
|
||||
@yt.playVideo()
|
||||
|
||||
pause: ->
|
||||
@paused = true
|
||||
if @yt and @yt.ready
|
||||
@yt.pauseVideo()
|
||||
|
||||
seekTo: (time) ->
|
||||
if @yt and @yt.ready
|
||||
@yt.seekTo(time, true)
|
||||
|
||||
setVolume: (volume) ->
|
||||
if @yt and @yt.ready
|
||||
if volume > 0
|
||||
# If the player is muted, even if the volume is set,
|
||||
# the player remains muted
|
||||
@yt.unMute()
|
||||
@yt.setVolume(volume * 100)
|
||||
|
||||
setQuality: (quality) ->
|
||||
# https://github.com/calzoneman/sync/issues/726
|
||||
|
||||
getTime: (cb) ->
|
||||
if @yt and @yt.ready
|
||||
cb(@yt.getCurrentTime())
|
||||
else
|
||||
cb(0)
|
||||
|
||||
getVolume: (cb) ->
|
||||
if @yt and @yt.ready
|
||||
if @yt.isMuted()
|
||||
cb(0)
|
||||
else
|
||||
cb(@yt.getVolume() / 100)
|
||||
else
|
||||
cb(VOLUME)
|
||||
|
|
@ -9,6 +9,4 @@ fi
|
|||
|
||||
echo "Building from src/ to lib/"
|
||||
npm run build-server
|
||||
echo "Building from player/ to www/js/player.js"
|
||||
npm run build-player
|
||||
echo "Done"
|
||||
|
|
|
|||
|
|
@ -317,9 +317,9 @@ html(lang="en")
|
|||
script(defer, src="https://player.vimeo.com/api/player.js")
|
||||
script(defer, src="/js/sc.js")
|
||||
script(defer, src="/js/video.js")
|
||||
script(defer, src="/js/playerjs-0.0.12.js")
|
||||
script(defer, src="/js/videojs-contrib-hls.min.js")
|
||||
script(defer, src="/js/videojs-resolution-switcher.js")
|
||||
script(defer, src="/js/playerjs-0.0.12.js")
|
||||
script(defer, src="/js/dash.all.min.js")
|
||||
script(defer, src="/js/videojs-dash.js")
|
||||
script(defer, src="https://player.twitch.tv/js/embed/v1.js")
|
||||
|
|
|
|||
Loading…
Reference in a new issue