import './videojs-components/peertube-load-progress-bar'
import './videojs-components/theater-button'
import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions, videojsUntyped } from './peertube-videojs-typings'
-import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils'
+import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig } from './utils'
import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n'
-import { Engine } from 'p2p-media-loader-hlsjs'
+import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
+import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed'
export type P2PMediaLoaderOptions = {
playlistUrl: string
+ segmentsSha256Url: string
+ trackerAnnounce: string[]
+ redundancyBaseUrls: string[]
+ videoFiles: VideoFile[]
}
export type CommonOptions = {
playerElement: HTMLVideoElement
+ onPlayerElementChange: (element: HTMLVideoElement) => void
autoplay: boolean
videoDuration: number
export type PeertubePlayerManagerOptions = {
common: CommonOptions,
- webtorrent?: WebtorrentOptions,
+ webtorrent: WebtorrentOptions,
p2pMediaLoader?: P2PMediaLoaderOptions
}
export class PeertubePlayerManager {
private static videojsLocaleCache: { [ path: string ]: any } = {}
+ private static playerElementClassName: string
static getServerTranslations (serverUrl: string, locale: string) {
const path = PeertubePlayerManager.getLocalePath(serverUrl, locale)
}
static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions) {
- if (mode === 'webtorrent') await import('./webtorrent-plugin')
- if (mode === 'p2p-media-loader') await import('./p2p-media-loader-plugin')
+ let p2pMediaLoader: any
- const videojsOptions = this.getVideojsOptions(mode, options)
+ this.playerElementClassName = options.common.playerElement.className
+
+ if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin')
+ if (mode === 'p2p-media-loader') {
+ [ p2pMediaLoader ] = await Promise.all([
+ import('p2p-media-loader-hlsjs'),
+ import('./p2p-media-loader/p2p-media-loader-plugin')
+ ])
+ }
+
+ const videojsOptions = this.getVideojsOptions(mode, options, p2pMediaLoader)
await this.loadLocaleInVideoJS(options.common.serverUrl, options.common.language)
videojs(options.common.playerElement, videojsOptions, function (this: any) {
const player = this
+ player.tech_.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
+ player.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
+
self.addContextMenu(mode, player, options.common.embedUrl)
return res(player)
})
}
+ private static async maybeFallbackToWebTorrent (currentMode: PlayerMode, player: any, options: PeertubePlayerManagerOptions) {
+ if (currentMode === 'webtorrent') return
+
+ console.log('Fallback to webtorrent.')
+
+ const newVideoElement = document.createElement('video')
+ newVideoElement.className = this.playerElementClassName
+
+ // VideoJS wraps our video element inside a div
+ let currentParentPlayerElement = options.common.playerElement.parentNode
+ // Fix on IOS, don't ask me why
+ if (!currentParentPlayerElement) currentParentPlayerElement = document.getElementById(options.common.playerElement.id).parentNode
+
+ currentParentPlayerElement.parentNode.insertBefore(newVideoElement, currentParentPlayerElement)
+
+ options.common.playerElement = newVideoElement
+ options.common.onPlayerElementChange(newVideoElement)
+
+ player.dispose()
+
+ await import('./webtorrent/webtorrent-plugin')
+
+ const mode = 'webtorrent'
+ const videojsOptions = this.getVideojsOptions(mode, options)
+
+ const self = this
+ videojs(newVideoElement, videojsOptions, function (this: any) {
+ const player = this
+
+ self.addContextMenu(mode, player, options.common.embedUrl)
+ })
+ }
+
private static loadLocaleInVideoJS (serverUrl: string, locale: string) {
const path = PeertubePlayerManager.getLocalePath(serverUrl, locale)
// It is the default locale, nothing to translate
return p.then(json => videojs.addLanguage(getShortLocale(completeLocale), json))
}
- private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions) {
+ private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions, p2pMediaLoaderModule?: any) {
const commonOptions = options.common
const webtorrentOptions = options.webtorrent
const p2pMediaLoaderOptions = options.p2pMediaLoader
+
+ let autoplay = options.common.autoplay
let html5 = {}
const plugins: VideoJSPluginOptions = {
peertube: {
- autoplay: commonOptions.autoplay, // Use peertube plugin autoplay because we get the file by webtorrent
+ mode,
+ autoplay, // Use peertube plugin autoplay because we get the file by webtorrent
videoViewUrl: commonOptions.videoViewUrl,
videoDuration: commonOptions.videoDuration,
startTime: commonOptions.startTime,
}
}
- if (p2pMediaLoaderOptions) {
+ if (mode === 'p2p-media-loader') {
const p2pMediaLoader: P2PMediaLoaderPluginOptions = {
+ redundancyBaseUrls: options.p2pMediaLoader.redundancyBaseUrls,
type: 'application/x-mpegURL',
src: p2pMediaLoaderOptions.playlistUrl
}
- const config = {
+ const trackerAnnounce = p2pMediaLoaderOptions.trackerAnnounce
+ .filter(t => t.startsWith('ws'))
+
+ const p2pMediaLoaderConfig = {
+ loader: {
+ trackerAnnounce,
+ segmentValidator: segmentValidatorFactory(options.p2pMediaLoader.segmentsSha256Url),
+ rtcConfig: getRtcConfig(),
+ requiredSegmentsPriority: 5,
+ segmentUrlBuilder: segmentUrlBuilderFactory(options.p2pMediaLoader.redundancyBaseUrls)
+ },
segments: {
- swarmId: 'swarm' // TODO: choose swarm id
+ swarmId: p2pMediaLoaderOptions.playlistUrl
}
}
const streamrootHls = {
+ levelLabelHandler: (level: { height: number, width: number }) => {
+ const file = p2pMediaLoaderOptions.videoFiles.find(f => f.resolution.id === level.height)
+
+ let label = file.resolution.label
+ if (file.fps >= 50) label += file.fps
+
+ return label
+ },
html5: {
hlsjsConfig: {
liveSyncDurationCount: 7,
- loader: new Engine(config).createLoaderClass()
+ loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass()
}
}
}
html5 = streamrootHls.html5
}
- if (webtorrentOptions) {
+ if (mode === 'webtorrent') {
const webtorrent = {
- autoplay: commonOptions.autoplay,
+ autoplay,
videoDuration: commonOptions.videoDuration,
playerElement: commonOptions.playerElement,
videoFiles: webtorrentOptions.videoFiles
}
Object.assign(plugins, { webtorrent })
+
+ // WebTorrent plugin handles autoplay, because we do some hackish stuff in there
+ autoplay = false
}
const videojsOptions = {
: undefined, // Undefined so the player knows it has to check the local storage
poster: commonOptions.poster,
- autoplay: false,
+ autoplay: autoplay === true ? 'any' : autoplay, // Use 'any' instead of true to get notifier by videojs if autoplay fails
inactivityTimeout: commonOptions.inactivityTimeout,
playbackRates: [ 0.5, 0.75, 1, 1.25, 1.5, 2 ],
plugins,