import 'videojs-contrib-quality-levels'
import './upnext/end-card'
import './upnext/upnext-plugin'
+import './stats/stats-card'
+import './stats/stats-plugin'
import './bezels/bezels-plugin'
import './peertube-plugin'
import './videojs-components/next-previous-video-button'
import './videojs-components/theater-button'
import './playlist/playlist-plugin'
import videojs from 'video.js'
+import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
+import { PluginsManager } from '@root-helpers/plugins-manager'
+import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
import { isDefaultLocale } from '@shared/core-utils/i18n'
import { VideoFile } from '@shared/models'
+import { copyToClipboard } from '../../root-helpers/utils'
import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
-import { getStoredP2PEnabled } from './peertube-player-local-storage'
+import { getAverageBandwidthInStore, getStoredP2PEnabled, saveAverageBandwidth } from './peertube-player-local-storage'
import {
NextPreviousVideoButtonOptions,
P2PMediaLoaderPluginOptions,
+ PeerTubeLinkButtonOptions,
+ PlayerNetworkInfo,
PlaylistPluginOptions,
UserWatching,
VideoJSCaption,
VideoJSPluginOptions
} from './peertube-videojs-typings'
import { TranslationsManager } from './translations-manager'
-import { buildVideoOrPlaylistEmbed, buildVideoLink, getRtcConfig, isSafari, isIOS } from './utils'
-import { copyToClipboard } from '../../root-helpers/utils'
+import { buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils'
// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
videoCaptions: VideoJSCaption[]
videoUUID: string
+ videoShortUUID: string
userWatching?: UserWatching
}
export type PeertubePlayerManagerOptions = {
- common: CommonOptions,
- webtorrent: WebtorrentOptions,
+ common: CommonOptions
+ webtorrent: WebtorrentOptions
p2pMediaLoader?: P2PMediaLoaderOptions
+
+ pluginsManager: PluginsManager
}
export class PeertubePlayerManager {
private static playerElementClassName: string
private static onPlayerChange: (player: videojs.Player) => void
private static alreadyPlayed = false
+ private static pluginsManager: PluginsManager
static initState () {
PeertubePlayerManager.alreadyPlayed = false
}
static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: videojs.Player) => void) {
+ this.pluginsManager = options.pluginsManager
+
let p2pMediaLoader: any
this.onPlayerChange = onPlayerChange
if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin')
if (mode === 'p2p-media-loader') {
[ p2pMediaLoader ] = await Promise.all([
- import('p2p-media-loader-hlsjs'),
+ import('@peertube/p2p-media-loader-hlsjs'),
import('./p2p-media-loader/p2p-media-loader-plugin')
])
}
- const videojsOptions = this.getVideojsOptions(mode, options, p2pMediaLoader)
+ const videojsOptions = await this.getVideojsOptions(mode, options, p2pMediaLoader)
await TranslationsManager.loadLocaleInVideoJS(options.common.serverUrl, options.common.language, videojs)
PeertubePlayerManager.alreadyPlayed = true
})
- self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle)
+ self.addContextMenu({
+ mode,
+ player,
+ videoShortUUID: options.common.videoShortUUID,
+ videoEmbedUrl: options.common.embedUrl,
+ videoEmbedTitle: options.common.embedTitle
+ })
player.bezels()
+ player.stats({
+ videoUUID: options.common.videoUUID,
+ videoIsLive: options.common.isLive,
+ mode
+ })
+
+ player.on('p2pInfo', (_, data: PlayerNetworkInfo) => {
+ if (data.source !== 'p2p-media-loader' || isNaN(data.bandwidthEstimate)) return
+
+ saveAverageBandwidth(data.bandwidthEstimate)
+ })
return res(player)
})
await import('./webtorrent/webtorrent-plugin')
const mode = 'webtorrent'
- const videojsOptions = this.getVideojsOptions(mode, options)
+ const videojsOptions = await this.getVideojsOptions(mode, options)
const self = this
videojs(newVideoElement, videojsOptions, function (this: videojs.Player) {
const player = this
- self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle)
+ self.addContextMenu({
+ mode,
+ player,
+ videoShortUUID: options.common.videoShortUUID,
+ videoEmbedUrl: options.common.embedUrl,
+ videoEmbedTitle: options.common.embedTitle
+ })
PeertubePlayerManager.onPlayerChange(player)
})
}
- private static getVideojsOptions (
+ private static async getVideojsOptions (
mode: PlayerMode,
options: PeertubePlayerManagerOptions,
p2pMediaLoaderModule?: any
- ): videojs.PlayerOptions {
+ ): Promise<videojs.PlayerOptions> {
const commonOptions = options.common
const isHLS = mode === 'p2p-media-loader'
let autoplay = this.getAutoPlayValue(commonOptions.autoplay)
- let html5 = {}
+ const html5 = {
+ preloadTextTracks: false
+ }
const plugins: VideoJSPluginOptions = {
peertube: {
if (isHLS) {
const { hlsjs } = PeertubePlayerManager.addP2PMediaLoaderOptions(plugins, options, p2pMediaLoaderModule)
- html5 = hlsjs.html5
+ Object.assign(html5, hlsjs.html5)
}
if (mode === 'webtorrent') {
controlBar: {
children: this.getControlBarChildren(mode, {
+ videoShortUUID: commonOptions.videoShortUUID,
+
captions: commonOptions.captions,
peertubeLink: commonOptions.peertubeLink,
theaterButton: commonOptions.theaterButton,
Object.assign(videojsOptions, { language: commonOptions.language })
}
- return videojsOptions
+ return this.pluginsManager.runHook('filter:internal.player.videojs.options.result', videojsOptions)
}
private static addP2PMediaLoaderOptions (
consumeOnly = true
}
- const p2pMediaLoaderConfig = {
+ const p2pMediaLoaderConfig: HlsJsEngineSettings = {
loader: {
trackerAnnounce,
segmentValidator: segmentValidatorFactory(options.p2pMediaLoader.segmentsSha256Url, options.common.isLive),
rtcConfig: getRtcConfig(),
requiredSegmentsPriority: 1,
+ simultaneousHttpDownloads: 1,
segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager),
useP2P: getStoredP2PEnabled(),
consumeOnly
swarmId: p2pMediaLoaderOptions.playlistUrl
}
}
+
const hlsjs = {
levelLabelHandler: (level: { height: number, width: number }) => {
const resolution = Math.min(level.height || 0, level.width || 0)
return label
},
html5: {
- hlsjsConfig: {
- capLevelToPlayerSize: true,
- autoStartLoad: false,
- liveSyncDurationCount: 5,
- loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass()
- }
+ hlsjsConfig: this.getHLSOptions(p2pMediaLoaderModule, p2pMediaLoaderConfig)
}
}
return toAssign
}
+ private static getHLSOptions (p2pMediaLoaderModule: any, p2pMediaLoaderConfig: HlsJsEngineSettings) {
+ const base = {
+ capLevelToPlayerSize: true,
+ autoStartLoad: false,
+ liveSyncDurationCount: 5,
+
+ loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass()
+ }
+
+ const averageBandwidth = getAverageBandwidthInStore()
+ if (!averageBandwidth) return base
+
+ return {
+ ...base,
+
+ abrEwmaDefaultEstimate: averageBandwidth * 8, // We want bit/s
+ startLevel: -1,
+ testBandwidth: false,
+ debug: false
+ }
+ }
+
private static addWebTorrentOptions (plugins: VideoJSPluginOptions, options: PeertubePlayerManagerOptions) {
const commonOptions = options.common
const webtorrentOptions = options.webtorrent
+ const p2pMediaLoaderOptions = options.p2pMediaLoader
const autoplay = this.getAutoPlayValue(commonOptions.autoplay) === 'play'
? true
autoplay,
videoDuration: commonOptions.videoDuration,
playerElement: commonOptions.playerElement,
- videoFiles: webtorrentOptions.videoFiles,
+ videoFiles: webtorrentOptions.videoFiles.length !== 0
+ ? webtorrentOptions.videoFiles
+ // The WebTorrent plugin won't be able to play these files, but it will fallback to HTTP mode
+ : p2pMediaLoaderOptions?.videoFiles || [],
startTime: commonOptions.startTime
}
}
private static getControlBarChildren (mode: PlayerMode, options: {
+ videoShortUUID: string
+
peertubeLink: boolean
theaterButton: boolean
captions: boolean
if (options.peertubeLink === true) {
Object.assign(children, {
- 'peerTubeLinkButton': {}
+ 'peerTubeLinkButton': { shortUUID: options.videoShortUUID } as PeerTubeLinkButtonOptions
})
}
return children
}
- private static addContextMenu (mode: PlayerMode, player: videojs.Player, videoEmbedUrl: string, videoEmbedTitle: string) {
+ private static addContextMenu (options: {
+ mode: PlayerMode
+ player: videojs.Player
+ videoShortUUID: string
+ videoEmbedUrl: string
+ videoEmbedTitle: string
+ }) {
+ const { mode, player, videoEmbedTitle, videoEmbedUrl, videoShortUUID } = options
+
const content = () => {
const isLoopEnabled = player.options_['loop']
const items = [
{
- label: isLoopEnabled ? player.localize('Stop playing in loop') : player.localize('Play in loop'),
+ icon: 'repeat',
+ label: player.localize('Play in loop') + (isLoopEnabled ? '<span class="vjs-icon-tick-white"></span>' : ''),
listener: function () {
player.options_['loop'] = !isLoopEnabled
}
{
label: player.localize('Copy the video URL'),
listener: function () {
- copyToClipboard(buildVideoLink())
+ copyToClipboard(buildVideoLink({ shortUUID: videoShortUUID }))
}
},
{
label: player.localize('Copy the video URL at the current time'),
listener: function (this: videojs.Player) {
- copyToClipboard(buildVideoLink({ startTime: this.currentTime() }))
+ const url = buildVideoLink({ shortUUID: videoShortUUID })
+
+ copyToClipboard(decorateVideoLink({ url, startTime: this.currentTime() }))
}
},
{
+ icon: 'code',
label: player.localize('Copy embed code'),
listener: () => {
copyToClipboard(buildVideoOrPlaylistEmbed(videoEmbedUrl, videoEmbedTitle))
})
}
- return items
+ items.push({
+ icon: 'info',
+ label: player.localize('Stats for nerds'),
+ listener: () => {
+ player.stats().show()
+ }
+ })
+
+ return items.map(i => ({
+ ...i,
+ label: `<span class="vjs-icon-${i.icon || 'link-2'}"></span>` + i.label
+ }))
}
// adding the menu