// Big thanks to: https://github.com/kmoskwiak/videojs-resolution-switcher
-import { VideoService } from '@app/shared/video/video.service'
import * as videojs from 'video.js'
import * as WebTorrent from 'webtorrent'
import { VideoFile } from '../../../../shared/models/videos/video.model'
class WebTorrentButton extends Button {
createEl () {
const div = document.createElement('div')
- const subDiv = document.createElement('div')
- div.appendChild(subDiv)
+ const subDivWebtorrent = document.createElement('div')
+ div.appendChild(subDivWebtorrent)
const downloadIcon = document.createElement('span')
downloadIcon.classList.add('icon', 'icon-download')
- subDiv.appendChild(downloadIcon)
+ subDivWebtorrent.appendChild(downloadIcon)
const downloadSpeedText = document.createElement('span')
downloadSpeedText.classList.add('download-speed-text')
const downloadSpeedUnit = document.createElement('span')
downloadSpeedText.appendChild(downloadSpeedNumber)
downloadSpeedText.appendChild(downloadSpeedUnit)
- subDiv.appendChild(downloadSpeedText)
+ subDivWebtorrent.appendChild(downloadSpeedText)
const uploadIcon = document.createElement('span')
uploadIcon.classList.add('icon', 'icon-upload')
- subDiv.appendChild(uploadIcon)
+ subDivWebtorrent.appendChild(uploadIcon)
const uploadSpeedText = document.createElement('span')
uploadSpeedText.classList.add('upload-speed-text')
const uploadSpeedUnit = document.createElement('span')
uploadSpeedText.appendChild(uploadSpeedNumber)
uploadSpeedText.appendChild(uploadSpeedUnit)
- subDiv.appendChild(uploadSpeedText)
+ subDivWebtorrent.appendChild(uploadSpeedText)
const peersText = document.createElement('span')
- peersText.textContent = ' peers'
peersText.classList.add('peers-text')
const peersNumber = document.createElement('span')
peersNumber.classList.add('peers-number')
- subDiv.appendChild(peersNumber)
- subDiv.appendChild(peersText)
+ subDivWebtorrent.appendChild(peersNumber)
+ subDivWebtorrent.appendChild(peersText)
- div.className = 'vjs-webtorrent'
+ div.className = 'vjs-peertube'
// Hide the stats before we get the info
- subDiv.className = 'vjs-webtorrent-hidden'
+ subDivWebtorrent.className = 'vjs-peertube-hidden'
+
+ const subDivHttp = document.createElement('div')
+ subDivHttp.className = 'vjs-peertube-hidden'
+ const subDivHttpText = document.createElement('span')
+ subDivHttpText.classList.add('peers-number')
+ subDivHttpText.textContent = 'HTTP'
+ const subDivFallbackText = document.createElement('span')
+ subDivFallbackText.classList.add('peers-text')
+ subDivFallbackText.textContent = ' fallback'
+
+ subDivHttp.appendChild(subDivHttpText)
+ subDivHttp.appendChild(subDivFallbackText)
+ div.appendChild(subDivHttp)
this.player_.peertube().on('torrentInfo', (event, data) => {
+ // We are in HTTP fallback
+ if (!data) {
+ subDivHttp.className = 'vjs-peertube-displayed'
+ subDivWebtorrent.className = 'vjs-peertube-hidden'
+
+ return
+ }
+
const downloadSpeed = bytes(data.downloadSpeed)
const uploadSpeed = bytes(data.uploadSpeed)
const numPeers = data.numPeers
- downloadSpeedNumber.textContent = downloadSpeed[0]
- downloadSpeedUnit.textContent = ' ' + downloadSpeed[1]
+ downloadSpeedNumber.textContent = downloadSpeed[ 0 ]
+ downloadSpeedUnit.textContent = ' ' + downloadSpeed[ 1 ]
- uploadSpeedNumber.textContent = uploadSpeed[0]
- uploadSpeedUnit.textContent = ' ' + uploadSpeed[1]
+ uploadSpeedNumber.textContent = uploadSpeed[ 0 ]
+ uploadSpeedUnit.textContent = ' ' + uploadSpeed[ 1 ]
peersNumber.textContent = numPeers
+ peersText.textContent = ' peers'
- subDiv.className = 'vjs-webtorrent-displayed'
+ subDivHttp.className = 'vjs-peertube-hidden'
+ subDivWebtorrent.className = 'vjs-peertube-displayed'
})
return div
private videoDuration: number
private videoViewInterval
private torrentInfoInterval
+ private savePlayerSrcFunction: Function
constructor (player: videojs.Player, options: PeertubePluginOptions) {
super(player, options)
this.videoViewUrl = options.videoViewUrl
this.videoDuration = options.videoDuration
+ this.savePlayerSrcFunction = this.player.src
// Hack to "simulate" src link in video.js >= 6
// Without this, we can't play the video after pausing it
// https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633
- this.player.src = function () {
- return true
- }
+ this.player.src = () => true
this.playerElement = options.playerElement
return
}
+ // Do not display error to user because we will have multiple fallbacks
+ this.disableErrorDisplay()
+ this.player.src = () => true
+
const previousVideoFile = this.currentVideoFile
this.currentVideoFile = videoFile
const options = { autoplay: true, controls: true }
renderVideo(torrent.files[0], this.playerElement, options,(err, renderer) => {
- if (err) return this.handleError(err)
+ if (err) return this.fallbackToHttp()
this.renderer = renderer
if (!this.player.paused()) {
flushVideoFile (videoFile: VideoFile, destroyRenderer = true) {
if (videoFile !== undefined && webtorrent.get(videoFile.magnetUri)) {
- if (destroyRenderer === true) this.renderer.destroy()
+ if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy()
+
webtorrent.remove(videoFile.magnetUri)
console.log('Removed ' + videoFile.magnetUri)
}
private runTorrentInfoScheduler () {
this.torrentInfoInterval = setInterval(() => {
- if (this.torrent !== undefined) {
- this.trigger('torrentInfo', {
- downloadSpeed: this.torrent.downloadSpeed,
- numPeers: this.torrent.numPeers,
- uploadSpeed: this.torrent.uploadSpeed
- })
- }
+ // Not initialized yet
+ if (this.torrent === undefined) return
+
+ // Http fallback
+ if (this.torrent === null) return this.trigger('torrentInfo', false)
+
+ return this.trigger('torrentInfo', {
+ downloadSpeed: this.torrent.downloadSpeed,
+ numPeers: this.torrent.numPeers,
+ uploadSpeed: this.torrent.uploadSpeed
+ })
}, 1000)
}
return fetch(this.videoViewUrl, { method: 'POST' })
}
+ private fallbackToHttp () {
+ this.flushVideoFile(this.currentVideoFile, true)
+ this.torrent = null
+
+ // Enable error display now this is our last fallback
+ this.player.one('error', () => this.enableErrorDisplay())
+
+ const httpUrl = this.currentVideoFile.fileUrl
+ this.player.src = this.savePlayerSrcFunction
+ this.player.src(httpUrl)
+ this.player.play()
+ }
+
private handleError (err: Error | string) {
return this.player.trigger('customError', { err })
}
+
+ private enableErrorDisplay () {
+ this.player.addClass('vjs-error-display-enabled')
+ }
+
+ private disableErrorDisplay () {
+ this.player.removeClass('vjs-error-display-enabled')
+ }
}
videojsUntyped.registerPlugin('peertube', PeerTubePlugin)
// Thanks: https://github.com/feross/render-media
// TODO: use render-media once https://github.com/feross/render-media/issues/32 is fixed
-import { extname } from 'path'
import * as MediaElementWrapper from 'mediasource'
+import { extname } from 'path'
import * as videostream from 'videostream'
const VIDEOSTREAM_EXTS = [
return renderMedia(file, elem, opts, callback)
}
-function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, callback: (err: Error, renderer: any) => void) {
+function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, callback: (err: Error, renderer?: any) => void) {
const extension = extname(file.name).toLowerCase()
let preparedElem = undefined
let currentTime = 0
function useVideostream () {
prepareElem()
- preparedElem.addEventListener('error', fallbackToMediaSource)
+ preparedElem.addEventListener('error', function onError () {
+ preparedElem.removeEventListener('error', onError)
+
+ return fallbackToMediaSource()
+ })
preparedElem.addEventListener('loadstart', onLoadStart)
return videostream(file, preparedElem)
}
- function useMediaSource () {
+ function useMediaSource (useVP9 = false) {
+ const codecs = getCodec(file.name, useVP9)
+
prepareElem()
- preparedElem.addEventListener('error', callback)
+ preparedElem.addEventListener('error', function onError(err) {
+ // Try with vp9 before returning an error
+ if (codecs.indexOf('vp8') !== -1) {
+ preparedElem.removeEventListener('error', onError)
+
+ return fallbackToMediaSource(true)
+ }
+
+ return callback(err)
+ })
preparedElem.addEventListener('loadstart', onLoadStart)
const wrapper = new MediaElementWrapper(preparedElem)
- const writable = wrapper.createWriteStream(getCodec(file.name))
+ const writable = wrapper.createWriteStream(codecs)
file.createReadStream().pipe(writable)
if (currentTime) preparedElem.currentTime = currentTime
return wrapper
}
- function fallbackToMediaSource () {
- preparedElem.removeEventListener('error', fallbackToMediaSource)
+ function fallbackToMediaSource (useVP9 = false) {
+ if (useVP9 === true) console.log('Falling back to media source with VP9 enabled.')
+ else console.log('Falling back to media source..')
- useMediaSource()
+ useMediaSource(useVP9)
}
function prepareElem () {
}
}
-function getCodec (name: string) {
+function getCodec (name: string, useVP9 = false) {
const ext = extname(name).toLowerCase()
- return {
- '.m4a': 'audio/mp4; codecs="mp4a.40.5"',
- '.m4v': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
- '.mkv': 'video/webm; codecs="avc1.640029, mp4a.40.5"',
- '.mp3': 'audio/mpeg',
- '.mp4': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
- '.webm': 'video/webm; codecs="opus, vorbis, vp8"'
- }[ext]
+ if (ext === '.mp4') {
+ return 'video/mp4; codecs="avc1.640029, mp4a.40.5"'
+ }
+
+ if (ext === '.webm') {
+ if (useVP9 === true) return 'video/webm; codecs="vp9, opus"'
+
+ return 'video/webm; codecs="vp8, vorbis"'
+ }
+
+ return undefined
}
export {