Initial decaf commit. Using bare pre-processed player.js instead of

decaffeinate.js
This commit is contained in:
rainbownapkin 2022-07-11 00:50:51 +00:00
parent 32dddab258
commit f3306f2263
26 changed files with 7078 additions and 1553 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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: ->

View file

@ -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)

View file

@ -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 shortmedium 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')

View file

@ -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

View file

@ -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 = '&times;'
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 = '&times;'
closeButton.onclick = ->
alertBox.parentNode.removeChild(alertBox)
alertBox.insertBefore(closeButton, alertBox.firstChild)
removeOld($('<div/>').append(alertBox))

View file

@ -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'
}
]

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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'
}
]

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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 ''

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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"

View file

@ -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")