Correctly implement p2p-media-loader
This commit is contained in:
parent
2adfc7ea9a
commit
3b6f205c34
12 changed files with 241 additions and 61 deletions
|
@ -87,6 +87,7 @@
|
||||||
"@ngx-translate/i18n-polyfill": "^1.0.0",
|
"@ngx-translate/i18n-polyfill": "^1.0.0",
|
||||||
"@streamroot/videojs-hlsjs-plugin": "^1.0.7",
|
"@streamroot/videojs-hlsjs-plugin": "^1.0.7",
|
||||||
"@types/core-js": "^2.5.0",
|
"@types/core-js": "^2.5.0",
|
||||||
|
"@types/hls.js": "^0.12.0",
|
||||||
"@types/jasmine": "^2.8.7",
|
"@types/jasmine": "^2.8.7",
|
||||||
"@types/jasminewd2": "^2.0.3",
|
"@types/jasminewd2": "^2.0.3",
|
||||||
"@types/jest": "^23.3.1",
|
"@types/jest": "^23.3.1",
|
||||||
|
@ -110,6 +111,7 @@
|
||||||
"extract-text-webpack-plugin": "4.0.0-beta.0",
|
"extract-text-webpack-plugin": "4.0.0-beta.0",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
"focus-visible": "^4.1.5",
|
"focus-visible": "^4.1.5",
|
||||||
|
"hls.js": "^0.12.2",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
|
|
|
@ -1,25 +1,45 @@
|
||||||
// FIXME: something weird with our path definition in tsconfig and typings
|
// FIXME: something weird with our path definition in tsconfig and typings
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as videojs from 'video.js'
|
import * as videojs from 'video.js'
|
||||||
import { P2PMediaLoaderPluginOptions, VideoJSComponentInterface } from './peertube-videojs-typings'
|
import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo, VideoJSComponentInterface } from './peertube-videojs-typings'
|
||||||
|
|
||||||
// videojs-hlsjs-plugin needs videojs in window
|
// videojs-hlsjs-plugin needs videojs in window
|
||||||
window['videojs'] = videojs
|
window['videojs'] = videojs
|
||||||
import '@streamroot/videojs-hlsjs-plugin'
|
import '@streamroot/videojs-hlsjs-plugin'
|
||||||
|
|
||||||
import { initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
|
import { Engine, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
|
||||||
|
import * as Hls from 'hls.js'
|
||||||
// import { Events } from '../p2p-media-loader/p2p-media-loader-core/lib'
|
import { Events } from 'p2p-media-loader-core'
|
||||||
|
|
||||||
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
|
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
|
||||||
class P2pMediaLoaderPlugin extends Plugin {
|
class P2pMediaLoaderPlugin extends Plugin {
|
||||||
|
|
||||||
|
private readonly CONSTANTS = {
|
||||||
|
INFO_SCHEDULER: 1000 // Don't change this
|
||||||
|
}
|
||||||
|
|
||||||
|
private hlsjs: Hls
|
||||||
|
private p2pEngine: Engine
|
||||||
|
private statsP2PBytes = {
|
||||||
|
pendingDownload: [] as number[],
|
||||||
|
pendingUpload: [] as number[],
|
||||||
|
numPeers: 0,
|
||||||
|
totalDownload: 0,
|
||||||
|
totalUpload: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private networkInfoInterval: any
|
||||||
|
|
||||||
constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) {
|
constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) {
|
||||||
super(player, options)
|
super(player, options)
|
||||||
|
|
||||||
initVideoJsContribHlsJsPlayer(player)
|
videojs.Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: Hls) => {
|
||||||
|
this.hlsjs = hlsjs
|
||||||
|
|
||||||
console.log(options)
|
this.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
|
initVideoJsContribHlsJsPlayer(player)
|
||||||
|
|
||||||
player.src({
|
player.src({
|
||||||
type: options.type,
|
type: options.type,
|
||||||
|
@ -27,6 +47,56 @@ class P2pMediaLoaderPlugin extends Plugin {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose () {
|
||||||
|
clearInterval(this.networkInfoInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
private initialize () {
|
||||||
|
this.p2pEngine = this.player.tech_.options_.hlsjsConfig.loader.getEngine()
|
||||||
|
|
||||||
|
this.hlsjs.on(Hls.Events.LEVEL_SWITCHING, (_, data: Hls.levelSwitchingData) => {
|
||||||
|
this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.runStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
private runStats () {
|
||||||
|
this.p2pEngine.on(Events.PieceBytesDownloaded, (method: string, size: number) => {
|
||||||
|
if (method === 'p2p') {
|
||||||
|
this.statsP2PBytes.pendingDownload.push(size)
|
||||||
|
this.statsP2PBytes.totalDownload += size
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, size: number) => {
|
||||||
|
if (method === 'p2p') {
|
||||||
|
this.statsP2PBytes.pendingUpload.push(size)
|
||||||
|
this.statsP2PBytes.totalUpload += size
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
|
||||||
|
this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--)
|
||||||
|
|
||||||
|
this.networkInfoInterval = setInterval(() => {
|
||||||
|
let downloadSpeed = this.statsP2PBytes.pendingDownload.reduce((a: number, b: number) => a + b, 0)
|
||||||
|
let uploadSpeed = this.statsP2PBytes.pendingUpload.reduce((a: number, b: number) => a + b, 0)
|
||||||
|
|
||||||
|
this.statsP2PBytes.pendingDownload = []
|
||||||
|
this.statsP2PBytes.pendingUpload = []
|
||||||
|
|
||||||
|
return this.player.trigger('p2pInfo', {
|
||||||
|
p2p: {
|
||||||
|
downloadSpeed,
|
||||||
|
uploadSpeed,
|
||||||
|
numPeers: this.statsP2PBytes.numPeers,
|
||||||
|
downloaded: this.statsP2PBytes.totalDownload,
|
||||||
|
uploaded: this.statsP2PBytes.totalUpload
|
||||||
|
}
|
||||||
|
} as PlayerNetworkInfo)
|
||||||
|
}, this.CONSTANTS.INFO_SCHEDULER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin)
|
videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin)
|
||||||
|
|
|
@ -24,17 +24,17 @@ videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitle
|
||||||
// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know)
|
// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know)
|
||||||
videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' '
|
videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' '
|
||||||
|
|
||||||
type PlayerMode = 'webtorrent' | 'p2p-media-loader'
|
export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
|
||||||
|
|
||||||
type WebtorrentOptions = {
|
export type WebtorrentOptions = {
|
||||||
videoFiles: VideoFile[]
|
videoFiles: VideoFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type P2PMediaLoaderOptions = {
|
export type P2PMediaLoaderOptions = {
|
||||||
playlistUrl: string
|
playlistUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommonOptions = {
|
export type CommonOptions = {
|
||||||
playerElement: HTMLVideoElement
|
playerElement: HTMLVideoElement
|
||||||
|
|
||||||
autoplay: boolean
|
autoplay: boolean
|
||||||
|
@ -137,6 +137,7 @@ export class PeertubePlayerManager {
|
||||||
const commonOptions = options.common
|
const commonOptions = options.common
|
||||||
const webtorrentOptions = options.webtorrent
|
const webtorrentOptions = options.webtorrent
|
||||||
const p2pMediaLoaderOptions = options.p2pMediaLoader
|
const p2pMediaLoaderOptions = options.p2pMediaLoader
|
||||||
|
let html5 = {}
|
||||||
|
|
||||||
const plugins: VideoJSPluginOptions = {
|
const plugins: VideoJSPluginOptions = {
|
||||||
peertube: {
|
peertube: {
|
||||||
|
@ -171,6 +172,7 @@ export class PeertubePlayerManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(plugins, { p2pMediaLoader, streamrootHls })
|
Object.assign(plugins, { p2pMediaLoader, streamrootHls })
|
||||||
|
html5 = streamrootHls.html5
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webtorrentOptions) {
|
if (webtorrentOptions) {
|
||||||
|
@ -184,6 +186,8 @@ export class PeertubePlayerManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
const videojsOptions = {
|
const videojsOptions = {
|
||||||
|
html5,
|
||||||
|
|
||||||
// We don't use text track settings for now
|
// We don't use text track settings for now
|
||||||
textTrackSettings: false,
|
textTrackSettings: false,
|
||||||
controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
|
controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
|
||||||
|
|
|
@ -2,7 +2,14 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as videojs from 'video.js'
|
import * as videojs from 'video.js'
|
||||||
import './videojs-components/settings-menu-button'
|
import './videojs-components/settings-menu-button'
|
||||||
import { PeerTubePluginOptions, UserWatching, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings'
|
import {
|
||||||
|
PeerTubePluginOptions,
|
||||||
|
ResolutionUpdateData,
|
||||||
|
UserWatching,
|
||||||
|
VideoJSCaption,
|
||||||
|
VideoJSComponentInterface,
|
||||||
|
videojsUntyped
|
||||||
|
} from './peertube-videojs-typings'
|
||||||
import { isMobile, timeToInt } from './utils'
|
import { isMobile, timeToInt } from './utils'
|
||||||
import {
|
import {
|
||||||
getStoredLastSubtitle,
|
getStoredLastSubtitle,
|
||||||
|
@ -30,6 +37,7 @@ class PeerTubePlugin extends Plugin {
|
||||||
private videoViewInterval: any
|
private videoViewInterval: any
|
||||||
private userWatchingVideoInterval: any
|
private userWatchingVideoInterval: any
|
||||||
private qualityObservationTimer: any
|
private qualityObservationTimer: any
|
||||||
|
private lastResolutionChange: ResolutionUpdateData
|
||||||
|
|
||||||
constructor (player: videojs.Player, options: PeerTubePluginOptions) {
|
constructor (player: videojs.Player, options: PeerTubePluginOptions) {
|
||||||
super(player, options)
|
super(player, options)
|
||||||
|
@ -44,6 +52,22 @@ class PeerTubePlugin extends Plugin {
|
||||||
this.player.ready(() => {
|
this.player.ready(() => {
|
||||||
const playerOptions = this.player.options_
|
const playerOptions = this.player.options_
|
||||||
|
|
||||||
|
if (this.player.webtorrent) {
|
||||||
|
this.player.webtorrent().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d))
|
||||||
|
this.player.webtorrent().on('autoResolutionChange', (_: any, d: any) => this.trigger('autoResolutionChange', d))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.player.p2pMediaLoader) {
|
||||||
|
this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.tech_.on('loadedqualitydata', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Replay a resolution change, now we loaded all quality data
|
||||||
|
if (this.lastResolutionChange) this.handleResolutionChange(this.lastResolutionChange)
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
|
||||||
const volume = getStoredVolume()
|
const volume = getStoredVolume()
|
||||||
if (volume !== undefined) this.player.volume(volume)
|
if (volume !== undefined) this.player.volume(volume)
|
||||||
|
|
||||||
|
@ -158,6 +182,21 @@ class PeerTubePlugin extends Plugin {
|
||||||
return fetch(url, { method: 'PUT', body, headers })
|
return fetch(url, { method: 'PUT', body, headers })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleResolutionChange (data: ResolutionUpdateData) {
|
||||||
|
this.lastResolutionChange = data
|
||||||
|
|
||||||
|
const qualityLevels = this.player.qualityLevels()
|
||||||
|
|
||||||
|
for (let i = 0; i < qualityLevels.length; i++) {
|
||||||
|
if (qualityLevels[i].height === data.resolutionId) {
|
||||||
|
data.id = qualityLevels[i].id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trigger('resolutionChange', data)
|
||||||
|
}
|
||||||
|
|
||||||
private alterInactivity () {
|
private alterInactivity () {
|
||||||
let saveInactivityTimeout: number
|
let saveInactivityTimeout: number
|
||||||
|
|
||||||
|
|
|
@ -83,13 +83,25 @@ type LoadedQualityData = {
|
||||||
type ResolutionUpdateData = {
|
type ResolutionUpdateData = {
|
||||||
auto: boolean,
|
auto: boolean,
|
||||||
resolutionId: number
|
resolutionId: number
|
||||||
|
id?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutoResolutionUpdateData = {
|
type AutoResolutionUpdateData = {
|
||||||
possible: boolean
|
possible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlayerNetworkInfo = {
|
||||||
|
p2p: {
|
||||||
|
downloadSpeed: number
|
||||||
|
uploadSpeed: number
|
||||||
|
downloaded: number
|
||||||
|
uploaded: number
|
||||||
|
numPeers: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
PlayerNetworkInfo,
|
||||||
ResolutionUpdateData,
|
ResolutionUpdateData,
|
||||||
AutoResolutionUpdateData,
|
AutoResolutionUpdateData,
|
||||||
VideoJSComponentInterface,
|
VideoJSComponentInterface,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
import { PlayerNetworkInfo, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||||
import { bytes } from '../utils'
|
import { bytes } from '../utils'
|
||||||
|
|
||||||
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
||||||
|
@ -65,7 +65,7 @@ class P2pInfoButton extends Button {
|
||||||
subDivHttp.appendChild(subDivHttpText)
|
subDivHttp.appendChild(subDivHttpText)
|
||||||
div.appendChild(subDivHttp)
|
div.appendChild(subDivHttp)
|
||||||
|
|
||||||
this.player_.on('p2pInfo', (event: any, data: any) => {
|
this.player_.on('p2pInfo', (event: any, data: PlayerNetworkInfo) => {
|
||||||
// We are in HTTP fallback
|
// We are in HTTP fallback
|
||||||
if (!data) {
|
if (!data) {
|
||||||
subDivHttp.className = 'vjs-peertube-displayed'
|
subDivHttp.className = 'vjs-peertube-displayed'
|
||||||
|
@ -74,11 +74,13 @@ class P2pInfoButton extends Button {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadSpeed = bytes(data.downloadSpeed)
|
const p2pStats = data.p2p
|
||||||
const uploadSpeed = bytes(data.uploadSpeed)
|
|
||||||
const totalDownloaded = bytes(data.downloaded)
|
const downloadSpeed = bytes(p2pStats.downloadSpeed)
|
||||||
const totalUploaded = bytes(data.uploaded)
|
const uploadSpeed = bytes(p2pStats.uploadSpeed)
|
||||||
const numPeers = data.numPeers
|
const totalDownloaded = bytes(p2pStats.downloaded)
|
||||||
|
const totalUploaded = bytes(p2pStats.uploaded)
|
||||||
|
const numPeers = p2pStats.numPeers
|
||||||
|
|
||||||
subDivWebtorrent.title = this.player_.localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' +
|
subDivWebtorrent.title = this.player_.localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' +
|
||||||
this.player_.localize('Total uploaded: ' + totalUploaded.join(' '))
|
this.player_.localize('Total uploaded: ' + totalUploaded.join(' '))
|
||||||
|
|
|
@ -14,11 +14,9 @@ class ResolutionMenuButton extends MenuButton {
|
||||||
super(player, options)
|
super(player, options)
|
||||||
this.player = player
|
this.player = player
|
||||||
|
|
||||||
player.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data))
|
player.tech_.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data))
|
||||||
|
|
||||||
if (player.webtorrent) {
|
player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0))
|
||||||
player.webtorrent().on('videoFileUpdate', () => setTimeout(() => this.trigger('updateLabel'), 0))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEl () {
|
createEl () {
|
||||||
|
@ -49,11 +47,32 @@ class ResolutionMenuButton extends MenuButton {
|
||||||
return 'vjs-resolution-control ' + super.buildWrapperCSSClass()
|
return 'vjs-resolution-control ' + super.buildWrapperCSSClass()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addClickListener (component: any) {
|
||||||
|
component.on('click', () => {
|
||||||
|
let children = this.menu.children()
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
if (component !== child) {
|
||||||
|
child.selected(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private buildQualities (data: LoadedQualityData) {
|
private buildQualities (data: LoadedQualityData) {
|
||||||
// The automatic resolution item will need other labels
|
// The automatic resolution item will need other labels
|
||||||
const labels: { [ id: number ]: string } = {}
|
const labels: { [ id: number ]: string } = {}
|
||||||
|
|
||||||
|
data.qualityData.video.sort((a, b) => {
|
||||||
|
if (a.id > b.id) return -1
|
||||||
|
if (a.id === b.id) return 0
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
|
||||||
for (const d of data.qualityData.video) {
|
for (const d of data.qualityData.video) {
|
||||||
|
// Skip auto resolution, we'll add it ourselves
|
||||||
|
if (d.id === -1) continue
|
||||||
|
|
||||||
this.menu.addChild(new ResolutionMenuItem(
|
this.menu.addChild(new ResolutionMenuItem(
|
||||||
this.player_,
|
this.player_,
|
||||||
{
|
{
|
||||||
|
@ -77,6 +96,12 @@ class ResolutionMenuButton extends MenuButton {
|
||||||
selected: true // By default, in auto mode
|
selected: true // By default, in auto mode
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
for (const m of this.menu.children()) {
|
||||||
|
this.addClickListener(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trigger('menuChanged')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResolutionMenuButton.prototype.controlText_ = 'Quality'
|
ResolutionMenuButton.prototype.controlText_ = 'Quality'
|
||||||
|
|
|
@ -28,30 +28,26 @@ class ResolutionMenuItem extends MenuItem {
|
||||||
this.id = options.id
|
this.id = options.id
|
||||||
this.callback = options.callback
|
this.callback = options.callback
|
||||||
|
|
||||||
if (player.webtorrent) {
|
player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data))
|
||||||
player.webtorrent().on('videoFileUpdate', (_: any, data: ResolutionUpdateData) => this.updateSelection(data))
|
|
||||||
|
|
||||||
// We only want to disable the "Auto" item
|
// We only want to disable the "Auto" item
|
||||||
if (this.id === -1) {
|
if (this.id === -1) {
|
||||||
player.webtorrent().on('autoResolutionUpdate', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data))
|
player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: update on HLS change
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick (event: any) {
|
handleClick (event: any) {
|
||||||
// Auto button disabled?
|
// Auto button disabled?
|
||||||
if (this.autoResolutionPossible === false && this.id === -1) return
|
if (this.autoResolutionPossible === false && this.id === -1) return
|
||||||
|
|
||||||
super.handleClick(event)
|
super.handleClick(event)
|
||||||
|
|
||||||
this.callback(this.id)
|
this.callback(this.id, 'video')
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelection (data: ResolutionUpdateData) {
|
updateSelection (data: ResolutionUpdateData) {
|
||||||
if (this.id === -1) {
|
if (this.id === -1) {
|
||||||
this.currentResolutionLabel = this.labels[data.resolutionId]
|
this.currentResolutionLabel = this.labels[data.id]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatic resolution only
|
// Automatic resolution only
|
||||||
|
@ -60,7 +56,7 @@ class ResolutionMenuItem extends MenuItem {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selected(this.id === data.resolutionId)
|
this.selected(this.id === data.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAutoResolution (data: AutoResolutionUpdateData) {
|
updateAutoResolution (data: AutoResolutionUpdateData) {
|
||||||
|
|
|
@ -223,6 +223,11 @@ class SettingsMenuItem extends MenuItem {
|
||||||
this.subMenu.on('updateLabel', () => {
|
this.subMenu.on('updateLabel', () => {
|
||||||
this.update()
|
this.update()
|
||||||
})
|
})
|
||||||
|
this.subMenu.on('menuChanged', () => {
|
||||||
|
this.bindClickEvents()
|
||||||
|
this.setSize()
|
||||||
|
this.update()
|
||||||
|
})
|
||||||
|
|
||||||
this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_)
|
this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_)
|
||||||
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
|
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
|
||||||
|
@ -230,7 +235,7 @@ class SettingsMenuItem extends MenuItem {
|
||||||
this.update()
|
this.update()
|
||||||
|
|
||||||
this.createBackButton()
|
this.createBackButton()
|
||||||
this.getSize()
|
this.setSize()
|
||||||
this.bindClickEvents()
|
this.bindClickEvents()
|
||||||
|
|
||||||
// prefixed event listeners for CSS TransitionEnd
|
// prefixed event listeners for CSS TransitionEnd
|
||||||
|
@ -292,8 +297,9 @@ class SettingsMenuItem extends MenuItem {
|
||||||
|
|
||||||
// save size of submenus on first init
|
// save size of submenus on first init
|
||||||
// if number of submenu items change dynamically more logic will be needed
|
// if number of submenu items change dynamically more logic will be needed
|
||||||
getSize () {
|
setSize () {
|
||||||
this.dialog.removeClass('vjs-hidden')
|
this.dialog.removeClass('vjs-hidden')
|
||||||
|
videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||||
this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
|
this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
|
||||||
this.setMargin()
|
this.setMargin()
|
||||||
this.dialog.addClass('vjs-hidden')
|
this.dialog.addClass('vjs-hidden')
|
||||||
|
|
|
@ -5,7 +5,7 @@ import * as videojs from 'video.js'
|
||||||
import * as WebTorrent from 'webtorrent'
|
import * as WebTorrent from 'webtorrent'
|
||||||
import { VideoFile } from '../../../../shared/models/videos/video.model'
|
import { VideoFile } from '../../../../shared/models/videos/video.model'
|
||||||
import { renderVideo } from './webtorrent/video-renderer'
|
import { renderVideo } from './webtorrent/video-renderer'
|
||||||
import { LoadedQualityData, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings'
|
import { LoadedQualityData, PlayerNetworkInfo, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings'
|
||||||
import { videoFileMaxByResolution, videoFileMinByResolution } from './utils'
|
import { videoFileMaxByResolution, videoFileMinByResolution } from './utils'
|
||||||
import { PeertubeChunkStore } from './webtorrent/peertube-chunk-store'
|
import { PeertubeChunkStore } from './webtorrent/peertube-chunk-store'
|
||||||
import {
|
import {
|
||||||
|
@ -180,7 +180,7 @@ class WebTorrentPlugin extends Plugin {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.changeQuality()
|
this.changeQuality()
|
||||||
this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id })
|
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
updateResolution (resolutionId: number, delay = 0) {
|
updateResolution (resolutionId: number, delay = 0) {
|
||||||
|
@ -216,15 +216,15 @@ class WebTorrentPlugin extends Plugin {
|
||||||
|
|
||||||
enableAutoResolution () {
|
enableAutoResolution () {
|
||||||
this.autoResolution = true
|
this.autoResolution = true
|
||||||
this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
|
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
|
||||||
}
|
}
|
||||||
|
|
||||||
disableAutoResolution (forbid = false) {
|
disableAutoResolution (forbid = false) {
|
||||||
if (forbid === true) this.autoResolutionPossible = false
|
if (forbid === true) this.autoResolutionPossible = false
|
||||||
|
|
||||||
this.autoResolution = false
|
this.autoResolution = false
|
||||||
this.trigger('autoResolutionUpdate', { possible: this.autoResolutionPossible })
|
this.trigger('autoResolutionChange', { possible: this.autoResolutionPossible })
|
||||||
this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
|
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
|
||||||
}
|
}
|
||||||
|
|
||||||
getTorrent () {
|
getTorrent () {
|
||||||
|
@ -472,12 +472,14 @@ class WebTorrentPlugin extends Plugin {
|
||||||
if (this.webtorrent.downloadSpeed !== 0) this.downloadSpeeds.push(this.webtorrent.downloadSpeed)
|
if (this.webtorrent.downloadSpeed !== 0) this.downloadSpeeds.push(this.webtorrent.downloadSpeed)
|
||||||
|
|
||||||
return this.player.trigger('p2pInfo', {
|
return this.player.trigger('p2pInfo', {
|
||||||
|
p2p: {
|
||||||
downloadSpeed: this.torrent.downloadSpeed,
|
downloadSpeed: this.torrent.downloadSpeed,
|
||||||
numPeers: this.torrent.numPeers,
|
numPeers: this.torrent.numPeers,
|
||||||
uploadSpeed: this.torrent.uploadSpeed,
|
uploadSpeed: this.torrent.uploadSpeed,
|
||||||
downloaded: this.torrent.downloaded,
|
downloaded: this.torrent.downloaded,
|
||||||
uploaded: this.torrent.uploaded
|
uploaded: this.torrent.uploaded
|
||||||
})
|
}
|
||||||
|
} as PlayerNetworkInfo)
|
||||||
}, this.CONSTANTS.INFO_SCHEDULER)
|
}, this.CONSTANTS.INFO_SCHEDULER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,7 +599,7 @@ class WebTorrentPlugin extends Plugin {
|
||||||
video: qualityLevelsPayload
|
video: qualityLevelsPayload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.player.trigger('loadedqualitydata', payload)
|
this.player.tech_.trigger('loadedqualitydata', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildQualityLabel (file: VideoFile) {
|
private buildQualityLabel (file: VideoFile) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared'
|
||||||
import { PeerTubeResolution } from '../player/definitions'
|
import { PeerTubeResolution } from '../player/definitions'
|
||||||
import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
|
import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
|
||||||
import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
|
import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
|
||||||
import { PeertubePlayerManager, PeertubePlayerManagerOptions } from '../../assets/player/peertube-player-manager'
|
import { PeertubePlayerManager, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Embed API exposes control of the embed player to the outside world via
|
* Embed API exposes control of the embed player to the outside world via
|
||||||
|
@ -162,6 +162,7 @@ class PeerTubeEmbed {
|
||||||
subtitle: string
|
subtitle: string
|
||||||
enableApi = false
|
enableApi = false
|
||||||
startTime: number | string = 0
|
startTime: number | string = 0
|
||||||
|
mode: PlayerMode
|
||||||
scope = 'peertube'
|
scope = 'peertube'
|
||||||
|
|
||||||
static async main () {
|
static async main () {
|
||||||
|
@ -255,6 +256,8 @@ class PeerTubeEmbed {
|
||||||
this.scope = this.getParamString(params, 'scope', this.scope)
|
this.scope = this.getParamString(params, 'scope', this.scope)
|
||||||
this.subtitle = this.getParamString(params, 'subtitle')
|
this.subtitle = this.getParamString(params, 'subtitle')
|
||||||
this.startTime = this.getParamString(params, 'start')
|
this.startTime = this.getParamString(params, 'start')
|
||||||
|
|
||||||
|
this.mode = this.getParamToggle(params, 'p2p-media-loader') ? 'p2p-media-loader' : 'webtorrent'
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Cannot get params from URL.', err)
|
console.error('Cannot get params from URL.', err)
|
||||||
}
|
}
|
||||||
|
@ -312,20 +315,26 @@ class PeerTubeEmbed {
|
||||||
serverUrl: window.location.origin,
|
serverUrl: window.location.origin,
|
||||||
language: navigator.language,
|
language: navigator.language,
|
||||||
embedUrl: window.location.origin + videoInfo.embedPath
|
embedUrl: window.location.origin + videoInfo.embedPath
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mode === 'p2p-media-loader') {
|
||||||
|
Object.assign(options, {
|
||||||
|
p2pMediaLoader: {
|
||||||
|
// playlistUrl: 'https://akamai-axtest.akamaized.net/routes/lapd-v1-acceptance/www_c4/Manifest.m3u8'
|
||||||
|
// playlistUrl: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8'
|
||||||
|
playlistUrl: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Object.assign(options, {
|
||||||
webtorrent: {
|
webtorrent: {
|
||||||
videoFiles: videoInfo.files
|
videoFiles: videoInfo.files
|
||||||
}
|
}
|
||||||
|
})
|
||||||
// p2pMediaLoader: {
|
|
||||||
// // playlistUrl: 'https://akamai-axtest.akamaized.net/routes/lapd-v1-acceptance/www_c4/Manifest.m3u8'
|
|
||||||
// // playlistUrl: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8'
|
|
||||||
// playlistUrl: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8'
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.player = await PeertubePlayerManager.initialize('webtorrent', options)
|
this.player = await PeertubePlayerManager.initialize(this.mode, options)
|
||||||
|
|
||||||
this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
|
this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
|
||||||
|
|
||||||
|
|
|
@ -411,6 +411,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.0.tgz#35cc282488de6f10af1d92902899a3b8ca3fbc47"
|
resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.0.tgz#35cc282488de6f10af1d92902899a3b8ca3fbc47"
|
||||||
integrity sha512-qjkHL3wF0JMHMqgm/kmL8Pf8rIiqvueEiZ0g6NVTcBX1WN46GWDr+V5z+gsHUeL0n8TfAmXnYmF7ajsxmBp4PQ==
|
integrity sha512-qjkHL3wF0JMHMqgm/kmL8Pf8rIiqvueEiZ0g6NVTcBX1WN46GWDr+V5z+gsHUeL0n8TfAmXnYmF7ajsxmBp4PQ==
|
||||||
|
|
||||||
|
"@types/hls.js@^0.12.0":
|
||||||
|
version "0.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/hls.js/-/hls.js-0.12.0.tgz#33f73e542201a766fa56792cb81fe9f97d7097ed"
|
||||||
|
integrity sha512-hJ7eJAQVEazAANK4Ay0YbXlZF36SDy9c8kcHTF7//77ylgV6hV/JrlwhVmobsSacr5aZcbw5MbZ2bSHbS36eOQ==
|
||||||
|
|
||||||
"@types/jasmine@*":
|
"@types/jasmine@*":
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.3.1.tgz#b6c4f356013364e98b583647c7b3b6de6fccd2cc"
|
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.3.1.tgz#b6c4f356013364e98b583647c7b3b6de6fccd2cc"
|
||||||
|
@ -3300,7 +3305,7 @@ etag@~1.8.1:
|
||||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
eventemitter3@^3.0.0:
|
eventemitter3@3.1.0, eventemitter3@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
|
||||||
integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==
|
integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==
|
||||||
|
@ -4236,6 +4241,14 @@ he@1.2.x:
|
||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
|
hls.js@^0.12.2:
|
||||||
|
version "0.12.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.12.2.tgz#64a969a78cc25991ed5de19357b1dc3f178ac23b"
|
||||||
|
integrity sha512-lQBSXggw9OzEuaUllUBoSxPcf7neFgnEiDRfCdCNdIPtUeV7vXZ0OeASx6EWtZTBiqSSPigoOX1Y+AR5dA1Feg==
|
||||||
|
dependencies:
|
||||||
|
eventemitter3 "3.1.0"
|
||||||
|
url-toolkit "^2.1.6"
|
||||||
|
|
||||||
hmac-drbg@^1.0.0:
|
hmac-drbg@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||||
|
@ -9976,7 +9989,7 @@ url-parse@^1.4.3:
|
||||||
querystringify "^2.0.0"
|
querystringify "^2.0.0"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
url-toolkit@^2.1.1, url-toolkit@^2.1.3:
|
url-toolkit@^2.1.1, url-toolkit@^2.1.3, url-toolkit@^2.1.6:
|
||||||
version "2.1.6"
|
version "2.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2"
|
resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2"
|
||||||
integrity sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==
|
integrity sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==
|
||||||
|
|
Loading…
Reference in a new issue