Merge branch 'next' into develop

This commit is contained in:
Chocobozzz 2021-10-11 09:37:30 +02:00
commit d00e9c54f3
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
25 changed files with 242 additions and 2878 deletions

View File

@ -128,7 +128,6 @@
"typescript": "~4.3.4", "typescript": "~4.3.4",
"video.js": "^7", "video.js": "^7",
"videojs-contextmenu-pt": "^5.4.1", "videojs-contextmenu-pt": "^5.4.1",
"videojs-contrib-quality-levels": "^2.0.9",
"videojs-dock": "^2.0.2", "videojs-dock": "^2.0.2",
"videojs-hotkeys": "^0.2.27", "videojs-hotkeys": "^0.2.27",
"videostream": "~3.2.1", "videostream": "~3.2.1",

View File

@ -1,9 +1,9 @@
// Thanks https://github.com/streamroot/videojs-hlsjs-plugin // Thanks https://github.com/streamroot/videojs-hlsjs-plugin
// We duplicated this plugin to choose the hls.js version we want, because streamroot only provide a bundled file // We duplicated this plugin to choose the hls.js version we want, because streamroot only provide a bundled file
import Hlsjs, { ErrorData, HlsConfig, Level, ManifestLoadedData } from 'hls.js' import Hlsjs, { ErrorData, HlsConfig, Level, LevelSwitchingData, ManifestParsedData } from 'hls.js'
import videojs from 'video.js' import videojs from 'video.js'
import { HlsjsConfigHandlerOptions, QualityLevelRepresentation, QualityLevels, VideoJSTechHLS } from '../peertube-videojs-typings' import { HlsjsConfigHandlerOptions, PeerTubeResolution, VideoJSTechHLS } from '../peertube-videojs-typings'
type ErrorCounts = { type ErrorCounts = {
[ type: string ]: number [ type: string ]: number
@ -102,15 +102,10 @@ class Html5Hlsjs {
private dvrDuration: number = null private dvrDuration: number = null
private edgeMargin: number = null private edgeMargin: number = null
private handlers: { [ id in 'play' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = { private handlers: { [ id in 'play' ]: EventListener } = {
play: null, play: null
playing: null,
textTracksChange: null,
audioTracksChange: null
} }
private uiTextTrackHandled = false
constructor (vjs: typeof videojs, source: videojs.Tech.SourceObject, tech: videojs.Tech) { constructor (vjs: typeof videojs, source: videojs.Tech.SourceObject, tech: videojs.Tech) {
this.vjs = vjs this.vjs = vjs
this.source = source this.source = source
@ -177,10 +172,6 @@ class Html5Hlsjs {
// See comment for `initialize` method. // See comment for `initialize` method.
dispose () { dispose () {
this.videoElement.removeEventListener('play', this.handlers.play) this.videoElement.removeEventListener('play', this.handlers.play)
this.videoElement.removeEventListener('playing', this.handlers.playing)
this.player.textTracks().removeEventListener('change', this.handlers.textTracksChange)
this.uiTextTrackHandled = false
this.hls.destroy() this.hls.destroy()
} }
@ -281,11 +272,7 @@ class Html5Hlsjs {
} }
} }
private switchQuality (qualityId: number) { private buildLevelLabel (level: Level) {
this.hls.currentLevel = qualityId
}
private _levelLabel (level: Level) {
if (this.player.srOptions_.levelLabelHandler) { if (this.player.srOptions_.levelLabelHandler) {
return this.player.srOptions_.levelLabelHandler(level as any) return this.player.srOptions_.levelLabelHandler(level as any)
} }
@ -294,167 +281,37 @@ class Html5Hlsjs {
if (level.width) return Math.round(level.width * 9 / 16) + 'p' if (level.width) return Math.round(level.width * 9 / 16) + 'p'
if (level.bitrate) return (level.bitrate / 1000) + 'kbps' if (level.bitrate) return (level.bitrate / 1000) + 'kbps'
return 0 return '0'
}
private _relayQualityChange (qualityLevels: QualityLevels) {
// Determine if it is "Auto" (all tracks enabled)
let isAuto = true
for (let i = 0; i < qualityLevels.length; i++) {
if (!qualityLevels[i]._enabled) {
isAuto = false
break
}
}
// Interact with ME
if (isAuto) {
this.hls.currentLevel = -1
return
}
// Find ID of highest enabled track
let selectedTrack: number
for (selectedTrack = qualityLevels.length - 1; selectedTrack >= 0; selectedTrack--) {
if (qualityLevels[selectedTrack]._enabled) {
break
}
}
this.hls.currentLevel = selectedTrack
}
private _handleQualityLevels () {
if (!this.metadata) return
const qualityLevels = this.player.qualityLevels?.()
if (!qualityLevels) return
for (let i = 0; i < this.metadata.levels.length; i++) {
const details = this.metadata.levels[i]
const representation: QualityLevelRepresentation = {
id: i,
width: details.width,
height: details.height,
bandwidth: details.bitrate,
bitrate: details.bitrate,
_enabled: true
}
const self = this
representation.enabled = function (this: QualityLevels, level: number, toggle?: boolean) {
// Brightcove switcher works TextTracks-style (enable tracks that it wants to ABR on)
if (typeof toggle === 'boolean') {
this[level]._enabled = toggle
self._relayQualityChange(this)
}
return this[level]._enabled
}
qualityLevels.addQualityLevel(representation)
}
} }
private _notifyVideoQualities () { private _notifyVideoQualities () {
if (!this.metadata) return if (!this.metadata) return
const cleanTracklist = []
if (this.metadata.levels.length > 1) { const resolutions: PeerTubeResolution[] = []
const autoLevel = {
id: -1,
label: 'auto',
selected: this.hls.manualLevel === -1
}
cleanTracklist.push(autoLevel)
}
this.metadata.levels.forEach((level, index) => { this.metadata.levels.forEach((level, index) => {
// Don't write in level (shared reference with Hls.js) resolutions.push({
const quality = {
id: index, id: index,
selected: index === this.hls.manualLevel, height: level.height,
label: this._levelLabel(level) width: level.width,
} bitrate: level.bitrate,
label: this.buildLevelLabel(level),
selected: level.id === this.hls.manualLevel,
cleanTracklist.push(quality) selectCallback: () => {
this.hls.currentLevel = index
}
})
}) })
const payload = { resolutions.push({
qualityData: { video: cleanTracklist }, id: -1,
qualitySwitchCallback: this.switchQuality.bind(this) label: this.player.localize('Auto'),
} selected: true,
selectCallback: () => this.hls.currentLevel = -1
})
this.tech.trigger('loadedqualitydata', payload) this.player.peertubeResolutions().add(resolutions)
// Self-de-register so we don't raise the payload multiple times
this.videoElement.removeEventListener('playing', this.handlers.playing)
}
private _updateSelectedAudioTrack () {
const playerAudioTracks = this.tech.audioTracks()
for (let j = 0; j < playerAudioTracks.length; j++) {
// FIXME: typings
if ((playerAudioTracks[j] as any).enabled) {
this.hls.audioTrack = j
break
}
}
}
private _onAudioTracks () {
const hlsAudioTracks = this.hls.audioTracks
const playerAudioTracks = this.tech.audioTracks()
if (hlsAudioTracks.length > 1 && playerAudioTracks.length === 0) {
// Add Hls.js audio tracks if not added yet
for (let i = 0; i < hlsAudioTracks.length; i++) {
playerAudioTracks.addTrack(new this.vjs.AudioTrack({
id: i.toString(),
kind: 'alternative',
label: hlsAudioTracks[i].name || hlsAudioTracks[i].lang,
language: hlsAudioTracks[i].lang,
enabled: i === this.hls.audioTrack
}))
}
// Handle audio track change event
this.handlers.audioTracksChange = this._updateSelectedAudioTrack.bind(this)
playerAudioTracks.addEventListener('change', this.handlers.audioTracksChange)
}
}
private _getTextTrackLabel (textTrack: TextTrack) {
// Label here is readable label and is optional (used in the UI so if it is there it should be different)
return textTrack.label ? textTrack.label : textTrack.language
}
private _isSameTextTrack (track1: TextTrack, track2: TextTrack) {
return this._getTextTrackLabel(track1) === this._getTextTrackLabel(track2) &&
track1.kind === track2.kind
}
private _updateSelectedTextTrack () {
const playerTextTracks = this.player.textTracks()
let activeTrack: TextTrack = null
for (let j = 0; j < playerTextTracks.length; j++) {
if (playerTextTracks[j].mode === 'showing') {
activeTrack = playerTextTracks[j]
break
}
}
const hlsjsTracks = this.videoElement.textTracks
for (let k = 0; k < hlsjsTracks.length; k++) {
if (hlsjsTracks[k].kind === 'subtitles' || hlsjsTracks[k].kind === 'captions') {
hlsjsTracks[k].mode = activeTrack && this._isSameTextTrack(hlsjsTracks[k], activeTrack)
? 'showing'
: 'disabled'
}
}
} }
private _startLoad () { private _startLoad () {
@ -472,97 +329,10 @@ class Html5Hlsjs {
return result return result
} }
private _filterDisplayableTextTracks (textTracks: TextTrackList) { private _onMetaData (_event: any, data: ManifestParsedData) {
const displayableTracks = []
// Filter out tracks that is displayable (captions or subtitles)
for (let idx = 0; idx < textTracks.length; idx++) {
if (textTracks[idx].kind === 'subtitles' || textTracks[idx].kind === 'captions') {
displayableTracks.push(textTracks[idx])
}
}
return displayableTracks
}
private _updateTextTrackList () {
const displayableTracks = this._filterDisplayableTextTracks(this.videoElement.textTracks)
const playerTextTracks = this.player.textTracks()
// Add stubs to make the caption switcher shows up
// Adding the Hls.js text track in will make us have double captions
for (let idx = 0; idx < displayableTracks.length; idx++) {
let isAdded = false
for (let jdx = 0; jdx < playerTextTracks.length; jdx++) {
if (this._isSameTextTrack(displayableTracks[idx], playerTextTracks[jdx])) {
isAdded = true
break
}
}
if (!isAdded) {
const hlsjsTextTrack = displayableTracks[idx]
this.player.addRemoteTextTrack({
kind: hlsjsTextTrack.kind as videojs.TextTrack.Kind,
label: this._getTextTrackLabel(hlsjsTextTrack),
language: hlsjsTextTrack.language,
srclang: hlsjsTextTrack.language
}, false)
}
}
// Handle UI switching
this._updateSelectedTextTrack()
if (!this.uiTextTrackHandled) {
this.handlers.textTracksChange = this._updateSelectedTextTrack.bind(this)
playerTextTracks.addEventListener('change', this.handlers.textTracksChange)
this.uiTextTrackHandled = true
}
}
private _onMetaData (_event: any, data: ManifestLoadedData) {
// This could arrive before 'loadedqualitydata' handlers is registered, remember it so we can raise it later // This could arrive before 'loadedqualitydata' handlers is registered, remember it so we can raise it later
this.metadata = data as any this.metadata = data
this._handleQualityLevels() this._notifyVideoQualities()
}
private _createCueHandler (captionConfig: any) {
return {
newCue: (track: any, startTime: number, endTime: number, captionScreen: { rows: any[] }) => {
let row: any
let cue: VTTCue
let text: string
const VTTCue = (window as any).VTTCue || (window as any).TextTrackCue
for (let r = 0; r < captionScreen.rows.length; r++) {
row = captionScreen.rows[r]
text = ''
if (!row.isEmpty()) {
for (let c = 0; c < row.chars.length; c++) {
text += row.chars[c].ucharj
}
cue = new VTTCue(startTime, endTime, text.trim())
// typeof null === 'object'
if (captionConfig != null && typeof captionConfig === 'object') {
// Copy client overridden property into the cue object
const configKeys = Object.keys(captionConfig)
for (let k = 0; k < configKeys.length; k++) {
cue[configKeys[k]] = captionConfig[configKeys[k]]
}
}
track.addCue(cue)
if (endTime === startTime) track.addCue(new VTTCue(endTime + 5, ''))
}
}
}
}
} }
private _initHlsjs () { private _initHlsjs () {
@ -577,11 +347,6 @@ class Html5Hlsjs {
this.hlsjsConfig.autoStartLoad = false this.hlsjsConfig.autoStartLoad = false
} }
const captionConfig = srOptions_?.captionConfig || techOptions.captionConfig
if (captionConfig) {
this.hlsjsConfig.cueHandler = this._createCueHandler(captionConfig)
}
// If the user explicitly sets autoStartLoad to false, we're not going to enter the if block above // If the user explicitly sets autoStartLoad to false, we're not going to enter the if block above
// That's why we have a separate if block here to set the 'play' listener // That's why we have a separate if block here to set the 'play' listener
if (this.hlsjsConfig.autoStartLoad === false) { if (this.hlsjsConfig.autoStartLoad === false) {
@ -589,17 +354,12 @@ class Html5Hlsjs {
this.videoElement.addEventListener('play', this.handlers.play) this.videoElement.addEventListener('play', this.handlers.play)
} }
// _notifyVideoQualities sometimes runs before the quality picker event handler is registered -> no video switcher
this.handlers.playing = this._notifyVideoQualities.bind(this)
this.videoElement.addEventListener('playing', this.handlers.playing)
this.hls = new Hlsjs(this.hlsjsConfig) this.hls = new Hlsjs(this.hlsjsConfig)
this._executeHooksFor('beforeinitialize') this._executeHooksFor('beforeinitialize')
this.hls.on(Hlsjs.Events.ERROR, (event, data) => this._onError(event, data)) this.hls.on(Hlsjs.Events.ERROR, (event, data) => this._onError(event, data))
this.hls.on(Hlsjs.Events.AUDIO_TRACKS_UPDATED, () => this._onAudioTracks()) this.hls.on(Hlsjs.Events.MANIFEST_PARSED, (event, data) => this._onMetaData(event, data))
this.hls.on(Hlsjs.Events.MANIFEST_PARSED, (event, data) => this._onMetaData(event, data as any)) // FIXME: typings
this.hls.on(Hlsjs.Events.LEVEL_LOADED, (event, data) => { this.hls.on(Hlsjs.Events.LEVEL_LOADED, (event, data) => {
// The DVR plugin will auto seek to "live edge" on start up // The DVR plugin will auto seek to "live edge" on start up
if (this.hlsjsConfig.liveSyncDuration) { if (this.hlsjsConfig.liveSyncDuration) {
@ -612,12 +372,25 @@ class Html5Hlsjs {
this.dvrDuration = data.details.totalduration this.dvrDuration = data.details.totalduration
this._duration = this.isLive ? Infinity : data.details.totalduration this._duration = this.isLive ? Infinity : data.details.totalduration
}) })
this.hls.once(Hlsjs.Events.FRAG_LOADED, () => { this.hls.once(Hlsjs.Events.FRAG_LOADED, () => {
// Emit custom 'loadedmetadata' event for parity with `videojs-contrib-hls` // Emit custom 'loadedmetadata' event for parity with `videojs-contrib-hls`
// Ref: https://github.com/videojs/videojs-contrib-hls#loadedmetadata // Ref: https://github.com/videojs/videojs-contrib-hls#loadedmetadata
this.tech.trigger('loadedmetadata') this.tech.trigger('loadedmetadata')
}) })
this.hls.on(Hlsjs.Events.LEVEL_SWITCHING, (_e, data: LevelSwitchingData) => {
const resolutionId = this.hls.autoLevelEnabled
? -1
: data.level
const autoResolutionChosenId = this.hls.autoLevelEnabled
? data.level
: -1
this.player.peertubeResolutions().select({ id: resolutionId, autoResolutionChosenId, byEngine: true })
})
this.hls.attachMedia(this.videoElement) this.hls.attachMedia(this.videoElement)
this.hls.loadSource(this.source.src) this.hls.loadSource(this.source.src)

View File

@ -116,14 +116,6 @@ class P2pMediaLoaderPlugin extends Plugin {
const options = this.player.tech(true).options_ as any const options = this.player.tech(true).options_ as any
this.p2pEngine = options.hlsjsConfig.loader.getEngine() this.p2pEngine = options.hlsjsConfig.loader.getEngine()
this.hlsjs.on(Hlsjs.Events.LEVEL_SWITCHING, (_: any, data: any) => {
this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height })
})
this.hlsjs.on(Hlsjs.Events.MANIFEST_LOADED, (_: any, data: any) => {
this.trigger('resolutionsLoaded')
})
this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => { this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => {
console.error('Segment error.', segment, err) console.error('Segment error.', segment, err)

View File

@ -1,13 +1,13 @@
import 'videojs-hotkeys/videojs.hotkeys' import 'videojs-hotkeys/videojs.hotkeys'
import 'videojs-dock' import 'videojs-dock'
import 'videojs-contextmenu-pt' import 'videojs-contextmenu-pt'
import 'videojs-contrib-quality-levels'
import './upnext/end-card' import './upnext/end-card'
import './upnext/upnext-plugin' import './upnext/upnext-plugin'
import './stats/stats-card' import './stats/stats-card'
import './stats/stats-plugin' import './stats/stats-plugin'
import './bezels/bezels-plugin' import './bezels/bezels-plugin'
import './peertube-plugin' import './peertube-plugin'
import './peertube-resolutions-plugin'
import './videojs-components/next-previous-video-button' import './videojs-components/next-previous-video-button'
import './videojs-components/p2p-info-button' import './videojs-components/p2p-info-button'
import './videojs-components/peertube-link-button' import './videojs-components/peertube-link-button'

View File

@ -1,4 +1,3 @@
import './videojs-components/settings-menu-button'
import videojs from 'video.js' import videojs from 'video.js'
import { timeToInt } from '@shared/core-utils' import { timeToInt } from '@shared/core-utils'
import { import {
@ -10,7 +9,7 @@ import {
saveVideoWatchHistory, saveVideoWatchHistory,
saveVolumeInStore saveVolumeInStore
} from './peertube-player-local-storage' } from './peertube-player-local-storage'
import { PeerTubePluginOptions, ResolutionUpdateData, UserWatching, VideoJSCaption } from './peertube-videojs-typings' import { PeerTubePluginOptions, UserWatching, VideoJSCaption } from './peertube-videojs-typings'
import { isMobile } from './utils' import { isMobile } from './utils'
const Plugin = videojs.getPlugin('plugin') const Plugin = videojs.getPlugin('plugin')
@ -27,7 +26,6 @@ class PeerTubePlugin extends Plugin {
private videoViewInterval: any private videoViewInterval: any
private userWatchingVideoInterval: any private userWatchingVideoInterval: any
private lastResolutionChange: ResolutionUpdateData
private isLive: boolean private isLive: boolean
@ -54,22 +52,6 @@ class PeerTubePlugin extends Plugin {
this.player.ready(() => { this.player.ready(() => {
const playerOptions = this.player.options_ const playerOptions = this.player.options_
if (options.mode === '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 (options.mode === 'p2p-media-loader') {
this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d))
}
this.player.tech(true).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)
@ -97,7 +79,7 @@ class PeerTubePlugin extends Plugin {
}) })
} }
this.player.textTracks().on('change', () => { this.player.textTracks().addEventListener('change', () => {
const showing = this.player.textTracks().tracks_.find(t => { const showing = this.player.textTracks().tracks_.find(t => {
return t.kind === 'captions' && t.mode === 'showing' return t.kind === 'captions' && t.mode === 'showing'
}) })
@ -216,22 +198,6 @@ 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
}
}
console.log('Resolution changed.', data)
this.trigger('resolutionChange', data)
}
private listenControlBarMouse () { private listenControlBarMouse () {
this.player.controlBar.on('mouseenter', () => { this.player.controlBar.on('mouseenter', () => {
this.mouseInControlBar = true this.mouseInControlBar = true

View File

@ -0,0 +1,88 @@
import videojs from 'video.js'
import { PeerTubeResolution } from './peertube-videojs-typings'
const Plugin = videojs.getPlugin('plugin')
class PeerTubeResolutionsPlugin extends Plugin {
private currentSelection: PeerTubeResolution
private resolutions: PeerTubeResolution[] = []
private autoResolutionChosenId: number
private autoResolutionEnabled = true
add (resolutions: PeerTubeResolution[]) {
for (const r of resolutions) {
this.resolutions.push(r)
}
this.currentSelection = this.getSelected()
this.sort()
this.trigger('resolutionsAdded')
}
getResolutions () {
return this.resolutions
}
getSelected () {
return this.resolutions.find(r => r.selected)
}
getAutoResolutionChosen () {
return this.resolutions.find(r => r.id === this.autoResolutionChosenId)
}
select (options: {
id: number
byEngine: boolean
autoResolutionChosenId?: number
}) {
const { id, autoResolutionChosenId, byEngine } = options
if (this.currentSelection?.id === id && this.autoResolutionChosenId === autoResolutionChosenId) return
this.autoResolutionChosenId = autoResolutionChosenId
for (const r of this.resolutions) {
r.selected = r.id === id
if (r.selected) {
this.currentSelection = r
if (!byEngine) r.selectCallback()
}
}
this.trigger('resolutionChanged')
}
disableAutoResolution () {
this.autoResolutionEnabled = false
this.trigger('autoResolutionEnabledChanged')
}
enabledAutoResolution () {
this.autoResolutionEnabled = true
this.trigger('autoResolutionEnabledChanged')
}
isAutoResolutionEnabeld () {
return this.autoResolutionEnabled
}
private sort () {
this.resolutions.sort((a, b) => {
if (a.id === -1) return 1
if (b.id === -1) return -1
if (a.height > b.height) return -1
if (a.height === b.height) return 0
return 1
})
}
}
videojs.registerPlugin('peertubeResolutions', PeerTubeResolutionsPlugin)
export { PeerTubeResolutionsPlugin }

View File

@ -1,6 +1,3 @@
// FIXME: lint
/* eslint-disable @typescript-eslint/ban-types */
import { HlsConfig, Level } from 'hls.js' import { HlsConfig, Level } from 'hls.js'
import videojs from 'video.js' import videojs from 'video.js'
import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models' import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models'
@ -8,11 +5,12 @@ import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin
import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
import { PlayerMode } from './peertube-player-manager' import { PlayerMode } from './peertube-player-manager'
import { PeerTubePlugin } from './peertube-plugin' import { PeerTubePlugin } from './peertube-plugin'
import { PeerTubeResolutionsPlugin } from './peertube-resolutions-plugin'
import { PlaylistPlugin } from './playlist/playlist-plugin' import { PlaylistPlugin } from './playlist/playlist-plugin'
import { EndCardOptions } from './upnext/end-card'
import { StatsCardOptions } from './stats/stats-card' import { StatsCardOptions } from './stats/stats-card'
import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
import { StatsForNerdsPlugin } from './stats/stats-plugin' import { StatsForNerdsPlugin } from './stats/stats-plugin'
import { EndCardOptions } from './upnext/end-card'
import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
declare module 'video.js' { declare module 'video.js' {
@ -37,16 +35,15 @@ declare module 'video.js' {
p2pMediaLoader (): P2pMediaLoaderPlugin p2pMediaLoader (): P2pMediaLoaderPlugin
peertubeResolutions (): PeerTubeResolutionsPlugin
contextmenuUI (options: any): any contextmenuUI (options: any): any
bezels (): void bezels (): void
stats (options?: StatsCardOptions): StatsForNerdsPlugin stats (options?: StatsCardOptions): StatsForNerdsPlugin
qualityLevels (): QualityLevels
textTracks (): TextTrackList & { textTracks (): TextTrackList & {
on: Function
tracks_: (TextTrack & { id: string, label: string, src: string })[] tracks_: (TextTrack & { id: string, label: string, src: string })[]
} }
@ -69,24 +66,16 @@ export interface HlsjsConfigHandlerOptions {
levelLabelHandler?: (level: Level) => string levelLabelHandler?: (level: Level) => string
} }
type QualityLevelRepresentation = { type PeerTubeResolution = {
id: number id: number
height: number
height?: number
label?: string label?: string
width?: number width?: number
bandwidth?: number
bitrate?: number bitrate?: number
enabled?: Function selected: boolean
_enabled: boolean selectCallback: () => void
}
type QualityLevels = QualityLevelRepresentation[] & {
selectedIndex: number
selectedIndex_: number
addQualityLevel (representation: QualityLevelRepresentation): void
} }
type VideoJSCaption = { type VideoJSCaption = {
@ -131,7 +120,7 @@ type PlaylistPluginOptions = {
type NextPreviousVideoButtonOptions = { type NextPreviousVideoButtonOptions = {
type: 'next' | 'previous' type: 'next' | 'previous'
handler: Function handler: () => void
isDisabled: () => boolean isDisabled: () => boolean
} }
@ -214,7 +203,7 @@ type PlayerNetworkInfo = {
type PlaylistItemOptions = { type PlaylistItemOptions = {
element: VideoPlaylistElement element: VideoPlaylistElement
onClicked: Function onClicked: () => void
} }
export { export {
@ -229,9 +218,8 @@ export {
PeerTubePluginOptions, PeerTubePluginOptions,
WebtorrentPluginOptions, WebtorrentPluginOptions,
P2PMediaLoaderPluginOptions, P2PMediaLoaderPluginOptions,
PeerTubeResolution,
VideoJSPluginOptions, VideoJSPluginOptions,
LoadedQualityData, LoadedQualityData,
QualityLevelRepresentation, PeerTubeLinkButtonOptions
PeerTubeLinkButtonOptions,
QualityLevels
} }

View File

@ -1,6 +1,4 @@
import videojs from 'video.js' import videojs from 'video.js'
import { LoadedQualityData } from '../peertube-videojs-typings'
import { ResolutionMenuItem } from './resolution-menu-item' import { ResolutionMenuItem } from './resolution-menu-item'
const Menu = videojs.getComponent('Menu') const Menu = videojs.getComponent('Menu')
@ -13,9 +11,12 @@ class ResolutionMenuButton extends MenuButton {
this.controlText('Quality') this.controlText('Quality')
player.tech(true).on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data)) player.peertubeResolutions().on('resolutionsAdded', () => this.buildQualities())
player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0)) // For parent
player.peertubeResolutions().on('resolutionChanged', () => {
setTimeout(() => this.trigger('labelUpdated'))
})
} }
createEl () { createEl () {
@ -58,20 +59,8 @@ class ResolutionMenuButton extends MenuButton {
}) })
} }
private buildQualities (data: LoadedQualityData) { private buildQualities () {
// The automatic resolution item will need other labels for (const d of this.player().peertubeResolutions().getResolutions()) {
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) {
// Skip auto resolution, we'll add it ourselves
if (d.id === -1) continue
const label = d.label === '0p' const label = d.label === '0p'
? this.player().localize('Audio-only') ? this.player().localize('Audio-only')
: d.label : d.label
@ -81,25 +70,11 @@ class ResolutionMenuButton extends MenuButton {
{ {
id: d.id, id: d.id,
label, label,
selected: d.selected, selected: d.selected
callback: data.qualitySwitchCallback
}) })
) )
labels[d.id] = d.label
} }
this.menu.addChild(new ResolutionMenuItem(
this.player_,
{
id: -1,
label: this.player_.localize('Auto'),
labels,
callback: data.qualitySwitchCallback,
selected: true // By default, in auto mode
}
))
for (const m of this.menu.children()) { for (const m of this.menu.children()) {
this.addClickListener(m) this.addClickListener(m)
} }

View File

@ -1,82 +1,72 @@
import videojs from 'video.js' import videojs from 'video.js'
import { AutoResolutionUpdateData, ResolutionUpdateData } from '../peertube-videojs-typings'
const MenuItem = videojs.getComponent('MenuItem') const MenuItem = videojs.getComponent('MenuItem')
export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions { export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions {
labels?: { [id: number]: string }
id: number id: number
callback: (resolutionId: number, type: 'video') => void
} }
class ResolutionMenuItem extends MenuItem { class ResolutionMenuItem extends MenuItem {
private readonly resolutionId: number private readonly resolutionId: number
private readonly label: string private readonly label: string
// Only used for the automatic item
private readonly labels: { [id: number]: string }
private readonly callback: (resolutionId: number, type: 'video') => void
private autoResolutionPossible: boolean private autoResolutionEnabled: boolean
private currentResolutionLabel: string private autoResolutionChosen: string
constructor (player: videojs.Player, options?: ResolutionMenuItemOptions) { constructor (player: videojs.Player, options?: ResolutionMenuItemOptions) {
options.selectable = true options.selectable = true
super(player, options) super(player, options)
this.autoResolutionPossible = true this.autoResolutionEnabled = true
this.currentResolutionLabel = '' this.autoResolutionChosen = ''
this.resolutionId = options.id this.resolutionId = options.id
this.label = options.label this.label = options.label
this.labels = options.labels
this.callback = options.callback
player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data)) player.peertubeResolutions().on('resolutionChanged', () => this.updateSelection())
// We only want to disable the "Auto" item // We only want to disable the "Auto" item
if (this.resolutionId === -1) { if (this.resolutionId === -1) {
player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data)) player.peertubeResolutions().on('autoResolutionEnabledChanged', () => this.updateAutoResolution())
} }
} }
handleClick (event: any) { handleClick (event: any) {
// Auto button disabled? // Auto button disabled?
if (this.autoResolutionPossible === false && this.resolutionId === -1) return if (this.autoResolutionEnabled === false && this.resolutionId === -1) return
super.handleClick(event) super.handleClick(event)
this.callback(this.resolutionId, 'video') this.player().peertubeResolutions().select({ id: this.resolutionId, byEngine: false })
} }
updateSelection (data: ResolutionUpdateData) { updateSelection () {
const selectedResolution = this.player().peertubeResolutions().getSelected()
if (this.resolutionId === -1) { if (this.resolutionId === -1) {
this.currentResolutionLabel = this.labels[data.id] this.autoResolutionChosen = this.player().peertubeResolutions().getAutoResolutionChosen()?.label
} }
// Automatic resolution only this.selected(this.resolutionId === selectedResolution.id)
if (data.auto === true) {
this.selected(this.resolutionId === -1)
return
}
this.selected(this.resolutionId === data.id)
} }
updateAutoResolution (data: AutoResolutionUpdateData) { updateAutoResolution () {
const enabled = this.player().peertubeResolutions().isAutoResolutionEnabeld()
// Check if the auto resolution is enabled or not // Check if the auto resolution is enabled or not
if (data.possible === false) { if (enabled === false) {
this.addClass('disabled') this.addClass('disabled')
} else { } else {
this.removeClass('disabled') this.removeClass('disabled')
} }
this.autoResolutionPossible = data.possible this.autoResolutionEnabled = enabled
} }
getLabel () { getLabel () {
if (this.resolutionId === -1) { if (this.resolutionId === -1) {
return this.label + ' <small>' + this.currentResolutionLabel + '</small>' return this.label + ' <small>' + this.autoResolutionChosen + '</small>'
} }
return this.label return this.label

View File

@ -248,7 +248,7 @@ class SettingsMenuItem extends MenuItem {
} }
build () { build () {
this.subMenu.on('updateLabel', () => { this.subMenu.on('labelUpdated', () => {
this.update() this.update()
}) })
this.subMenu.on('menuChanged', () => { this.subMenu.on('menuChanged', () => {

View File

@ -9,7 +9,7 @@ import {
getStoredVolume, getStoredVolume,
saveAverageBandwidth saveAverageBandwidth
} from '../peertube-player-local-storage' } from '../peertube-player-local-storage'
import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils' import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
import { PeertubeChunkStore } from './peertube-chunk-store' import { PeertubeChunkStore } from './peertube-chunk-store'
import { renderVideo } from './video-renderer' import { renderVideo } from './video-renderer'
@ -175,11 +175,10 @@ class WebTorrentPlugin extends Plugin {
return done() return done()
}) })
this.changeQuality() this.selectAppropriateResolution(true)
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id })
} }
updateResolution (resolutionId: number, delay = 0) { updateEngineResolution (resolutionId: number, delay = 0) {
// Remember player state // Remember player state
const currentTime = this.player.currentTime() const currentTime = this.player.currentTime()
const isPaused = this.player.paused() const isPaused = this.player.paused()
@ -219,17 +218,10 @@ class WebTorrentPlugin extends Plugin {
} }
} }
enableAutoResolution () { disableAutoResolution () {
this.autoResolution = true
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
}
disableAutoResolution (forbid = false) {
if (forbid === true) this.autoResolutionPossible = false
this.autoResolution = false this.autoResolution = false
this.trigger('autoResolutionChange', { possible: this.autoResolutionPossible }) this.autoResolutionPossible = false
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) this.player.peertubeResolutions().disableAutoResolution()
} }
isAutoResolutionPossible () { isAutoResolutionPossible () {
@ -244,6 +236,22 @@ class WebTorrentPlugin extends Plugin {
return this.currentVideoFile return this.currentVideoFile
} }
changeQuality (id: number) {
if (id === -1) {
if (this.autoResolutionPossible === true) {
this.autoResolution = true
this.selectAppropriateResolution(false)
}
return
}
this.autoResolution = false
this.updateEngineResolution(id)
this.selectAppropriateResolution(false)
}
private addTorrent ( private addTorrent (
magnetOrTorrentUrl: string, magnetOrTorrentUrl: string,
previousVideoFile: VideoFile, previousVideoFile: VideoFile,
@ -466,7 +474,7 @@ class WebTorrentPlugin extends Plugin {
} }
if (changeResolution === true) { if (changeResolution === true) {
this.updateResolution(file.resolution.id, changeResolutionDelay) this.updateEngineResolution(file.resolution.id, changeResolutionDelay)
// Wait some seconds in observation of our new resolution // Wait some seconds in observation of our new resolution
this.isAutoResolutionObservation = true this.isAutoResolutionObservation = true
@ -516,7 +524,7 @@ class WebTorrentPlugin extends Plugin {
private fallbackToHttp (options: PlayOptions, done?: (err?: Error) => void) { private fallbackToHttp (options: PlayOptions, done?: (err?: Error) => void) {
const paused = this.player.paused() const paused = this.player.paused()
this.disableAutoResolution(true) this.disableAutoResolution()
this.flushVideoFile(this.currentVideoFile, true) this.flushVideoFile(this.currentVideoFile, true)
this.torrent = null this.torrent = null
@ -528,7 +536,7 @@ class WebTorrentPlugin extends Plugin {
this.player.src = this.savePlayerSrcFunction this.player.src = this.savePlayerSrcFunction
this.player.src(httpUrl) this.player.src(httpUrl)
this.changeQuality() this.selectAppropriateResolution(true)
// We changed the source, so reinit captions // We changed the source, so reinit captions
this.player.trigger('sourcechange') this.player.trigger('sourcechange')
@ -601,32 +609,22 @@ class WebTorrentPlugin extends Plugin {
} }
private buildQualities () { private buildQualities () {
const qualityLevelsPayload = [] const resolutions: PeerTubeResolution[] = this.videoFiles.map(file => ({
id: file.resolution.id,
label: this.buildQualityLabel(file),
height: file.resolution.id,
selected: false,
selectCallback: () => this.changeQuality(file.resolution.id)
}))
for (const file of this.videoFiles) { resolutions.push({
const representation = { id: -1,
id: file.resolution.id, label: this.player.localize('Auto'),
label: this.buildQualityLabel(file), selected: true,
height: file.resolution.id, selectCallback: () => this.changeQuality(-1)
_enabled: true })
}
this.player.qualityLevels().addQualityLevel(representation) this.player.peertubeResolutions().add(resolutions)
qualityLevelsPayload.push({
id: representation.id,
label: representation.label,
selected: false
})
}
const payload: LoadedQualityData = {
qualitySwitchCallback: (d: any) => this.qualitySwitchCallback(d),
qualityData: {
video: qualityLevelsPayload
}
}
this.player.tech(true).trigger('loadedqualitydata', payload)
} }
private buildQualityLabel (file: VideoFile) { private buildQualityLabel (file: VideoFile) {
@ -639,29 +637,16 @@ class WebTorrentPlugin extends Plugin {
return label return label
} }
private qualitySwitchCallback (id: number) { private selectAppropriateResolution (byEngine: boolean) {
if (id === -1) { const resolution = this.autoResolution
if (this.autoResolutionPossible === true) this.enableAutoResolution() ? -1
return : this.getCurrentResolutionId()
}
this.disableAutoResolution() const autoResolutionChosen = this.autoResolution
this.updateResolution(id) ? this.getCurrentResolutionId()
} : undefined
private changeQuality () { this.player.peertubeResolutions().select({ id: resolution, autoResolutionChosenId: autoResolutionChosen, byEngine })
const resolutionId = this.currentVideoFile.resolution.id as number
const qualityLevels = this.player.qualityLevels()
if (resolutionId === -1) {
qualityLevels.selectedIndex = -1
return
}
for (let i = 0; i < qualityLevels.length; i++) {
const q = qualityLevels[i]
if (q.height === resolutionId) qualityLevels.selectedIndex_ = i
}
} }
} }

View File

@ -64,19 +64,12 @@ export class PeerTubeEmbedApi {
if (this.isWebtorrent()) { if (this.isWebtorrent()) {
if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionPossible() === false) return if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionPossible() === false) return
// Auto resolution this.embed.player.webtorrent().changeQuality(resolutionId)
if (resolutionId === -1) {
this.embed.player.webtorrent().enableAutoResolution()
return
}
this.embed.player.webtorrent().disableAutoResolution()
this.embed.player.webtorrent().updateResolution(resolutionId)
return return
} }
this.embed.player.p2pMediaLoader().getHLSJS().nextLevel = resolutionId this.embed.player.p2pMediaLoader().getHLSJS().currentLevel = resolutionId
} }
private getCaptions (): PeerTubeTextTrack[] { private getCaptions (): PeerTubeTextTrack[] {
@ -139,15 +132,10 @@ export class PeerTubeEmbedApi {
}) })
// PeerTube specific capabilities // PeerTube specific capabilities
if (this.isWebtorrent()) { this.embed.player.peertubeResolutions().on('resolutionsAdded', () => this.loadResolutions())
this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions()) this.embed.player.peertubeResolutions().on('resolutionChanged', () => this.loadResolutions())
this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions())
this.loadWebTorrentResolutions() this.loadResolutions()
} else {
this.embed.player.p2pMediaLoader().on('resolutionChange', () => this.loadP2PMediaLoaderResolutions())
this.embed.player.p2pMediaLoader().on('resolutionsLoaded', () => this.loadP2PMediaLoaderResolutions())
}
this.embed.player.on('volumechange', () => { this.embed.player.on('volumechange', () => {
this.channel.notify({ this.channel.notify({
@ -157,49 +145,15 @@ export class PeerTubeEmbedApi {
}) })
} }
private loadWebTorrentResolutions () { private loadResolutions () {
this.resolutions = [] this.resolutions = this.embed.player.peertubeResolutions().getResolutions()
.map(r => ({
const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId() id: r.id,
label: r.label,
for (const videoFile of this.embed.player.webtorrent().videoFiles) { active: r.selected,
let label = videoFile.resolution.label width: r.width,
if (videoFile.fps && videoFile.fps >= 50) { height: r.height
label += videoFile.fps }))
}
this.resolutions.push({
id: videoFile.resolution.id,
label,
src: videoFile.magnetUri,
active: videoFile.resolution.id === currentResolutionId,
height: videoFile.resolution.id
})
}
this.channel.notify({
method: 'resolutionUpdate',
params: this.resolutions
})
}
private loadP2PMediaLoaderResolutions () {
this.resolutions = []
const qualityLevels = this.embed.player.qualityLevels()
const currentResolutionId = this.embed.player.qualityLevels().selectedIndex
for (let i = 0; i < qualityLevels.length; i++) {
const level = qualityLevels[i]
this.resolutions.push({
id: level.id,
label: level.height + 'p',
active: level.id === currentResolutionId,
width: level.width,
height: level.height
})
}
this.channel.notify({ this.channel.notify({
method: 'resolutionUpdate', method: 'resolutionUpdate',

View File

@ -86,8 +86,6 @@ window.addEventListener('load', async () => {
captionEl.innerHTML = '' captionEl.innerHTML = ''
captions.forEach(c => { captions.forEach(c => {
console.log(c)
if (c.mode === 'showing') { if (c.mode === 'showing') {
const itemEl = document.createElement('strong') const itemEl = document.createElement('strong')
itemEl.innerText = `${c.label} (active)` itemEl.innerText = `${c.label} (active)`

View File

@ -12618,14 +12618,6 @@ videojs-contextmenu-pt@^5.4.1:
global "^4.4.0" global "^4.4.0"
video.js "^7.6.0" video.js "^7.6.0"
videojs-contrib-quality-levels@^2.0.9:
version "2.1.0"
resolved "https://registry.yarnpkg.com/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.1.0.tgz#046e9e21ed01043f512b83a1916001d552457083"
integrity sha512-dqGQGbL9AFhucxki7Zh0c3kIhH0PAPcHEh6jUdRyaFCVeOuqnJrOYs/3wNtsokDdBdRf2Du2annpu4Z2XaSZRg==
dependencies:
global "^4.3.2"
video.js "^6 || ^7"
videojs-dock@^2.0.2: videojs-dock@^2.0.2:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/videojs-dock/-/videojs-dock-2.2.0.tgz#57e4f942da1b8e930c4387fed85942473bc40829" resolved "https://registry.yarnpkg.com/videojs-dock/-/videojs-dock-2.2.0.tgz#57e4f942da1b8e930c4387fed85942473bc40829"

View File

@ -198,8 +198,6 @@
"eslint-plugin-standard": "^5.0.0", "eslint-plugin-standard": "^5.0.0",
"fast-xml-parser": "^3.19.0", "fast-xml-parser": "^3.19.0",
"maildev": "^1.0.0-rc3", "maildev": "^1.0.0-rc3",
"marked": "^3.0.2",
"marked-man": "^0.7.0",
"mocha": "^9.0.0", "mocha": "^9.0.0",
"nodemon": "^2.0.1", "nodemon": "^2.0.1",
"proxy": "^1.0.2", "proxy": "^1.0.2",

View File

@ -1,5 +0,0 @@
#!/bin/sh
set -eu
node_modules/marked-man/bin/marked-man server/tools/README.md > dist/server/tools/peertube.8

View File

@ -1,82 +1,3 @@
peertube(8) -- companion CLI for PeerTube # PeerTube CLI
=========================================
SYNOPSIS See https://docs.joinpeertube.org/maintain-tools?id=remote-tools
--------
```
peertube [command] [options]
```
DESCRIPTION
-----------
`peertube` wraps various utilities around PeerTube that are used either on a running local, running remote, or cold local instance.
COMMANDS
--------
Unless otherwise specified, every command can be queried for its own help or manual by passing its name to the `help` command, or by using the `--help` option.
`auth [action]`: stores credentials for your accounts on remote instances, so that you don't need to pass them at every command
`upload|up`: upload a video to a remote instance
$ peertube upload \
-u "PEERTUBE_URL" \
-U "PEERTUBE_USER" \
--password "PEERTUBE_PASSWORD"
`import-videos|import`: import a video from a streaming platform to a remote instance
$ peertube import \
-u "PEERTUBE_URL" \
-U "PEERTUBE_USER" \
--password "PEERTUBE_PASSWORD" \
-t "TARGET_URL"
The target URL can be directly the video file, or any of the supported sites of youtube-dl. The video is downloaded locally and then uploaded. Already downloaded videos will not be uploaded twice, so you can run and re-run the script in case of crash, disconnection…
`watch|w`: watch a video in the terminal ✩°。⋆
-g, --gui <player> player type (default: ascii)
-i, --invert invert colors (ascii player only)
-r, --resolution <res> video resolution (default: 720)
It provides support for different players:
- ascii (default ; plays in ascii art in your terminal!)
- mpv
- mplayer
- vlc
- stdout
- xbmc
- airplay
- chromecast
`repl`: interact with the application libraries and objects even when PeerTube is not running
Type .help to see the repl-only functions, or to see the available PeerTube core functions:
repl> lodash.keys(context)
`help [cmd]`: display help for [cmd]
EXAMPLES
--------
$ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"
$ peertube up <videoFile>
$ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10
SEE ALSO
--------
[PeerTube Tools Documentation](https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/tools.md)
[PeerTube Admin Documentation](https://docs.joinpeertube.org/lang/en/docs/)
REPORTING BUGS
--------------
See [PeerTube repository](https://github.com/Chocobozzz/PeerTube).

View File

@ -5,8 +5,7 @@
"dependencies": { "dependencies": {
"application-config": "^2.0.0", "application-config": "^2.0.0",
"cli-table3": "^0.6.0", "cli-table3": "^0.6.0",
"netrc-parser": "^3.1.6", "netrc-parser": "^3.1.6"
"webtorrent-hybrid": "^4.0.3"
}, },
"devDependencies": {} "devDependencies": {}
} }

View File

@ -1,83 +0,0 @@
import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
import * as repl from 'repl'
import * as path from 'path'
import * as _ from 'lodash'
import * as Sequelize from 'sequelize'
import * as YoutubeDL from 'youtube-dl'
import { initDatabaseModels, sequelizeTypescript } from '../initializers/database'
import * as cli from '../tools/cli'
import { logger } from '../helpers/logger'
import * as constants from '../initializers/constants'
import * as modelsUtils from '../models/utils'
import * as coreUtils from '../helpers/core-utils'
import * as ffmpegUtils from '../helpers/ffmpeg-utils'
import * as peertubeCryptoUtils from '../helpers/peertube-crypto'
import * as utils from '../helpers/utils'
import * as YoutubeDLUtils from '../helpers/youtube-dl'
const start = async () => {
await initDatabaseModels(true)
const versionCommitHash = await utils.getServerCommit()
const initContext = (replServer) => {
return (context) => {
const properties = {
context,
repl: replServer,
env: process.env,
lodash: _,
path,
cli,
logger,
constants,
Sequelize,
sequelizeTypescript,
modelsUtils,
models: sequelizeTypescript.models,
transaction: sequelizeTypescript.transaction,
query: sequelizeTypescript.query,
queryInterface: sequelizeTypescript.getQueryInterface(),
YoutubeDL,
coreUtils,
ffmpegUtils,
peertubeCryptoUtils,
utils,
YoutubeDLUtils
}
for (const prop in properties) {
Object.defineProperty(context, prop, {
configurable: false,
enumerable: true,
value: properties[prop]
})
}
}
}
const replServer = repl.start({
prompt: `PeerTube [${cli.version}] (${versionCommitHash})> `
})
initContext(replServer)(replServer.context)
replServer.on('reset', initContext(replServer))
replServer.on('exit', () => process.exit())
const resetCommand = {
help: 'Reset REPL',
action () {
this.write('.clear\n')
this.displayPrompt()
}
}
replServer.defineCommand('reset', resetCommand)
replServer.defineCommand('r', resetCommand)
}
start()
.catch((err) => {
console.error(err)
})

View File

@ -1,42 +0,0 @@
import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
import { program, Option, OptionValues } from 'commander'
import { join } from 'path'
import { execSync } from 'child_process'
program
.name('watch')
.arguments('<url>')
.addOption(
new Option('-g, --gui <player>', 'player type')
.default('vlc')
.choices([ 'airplay', 'stdout', 'chromecast', 'mpv', 'vlc', 'mplayer', 'xbmc' ])
)
.option('-r, --resolution <res>', 'video resolution', '480')
.addHelpText('after', '\n\n Examples:\n\n' +
' $ peertube watch -g mpv https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n' +
' $ peertube watch --gui stdout https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n' +
' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n'
)
.action((url, options) => run(url, options))
.parse(process.argv)
function run (url: string, options: OptionValues) {
if (!url) {
console.error('<url> positional argument is required.')
process.exit(-1)
}
const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js')
const args = ` --${options.gui} ` +
url.replace(/(\/videos\/watch\/)|\/w\//, '/download/torrents/') +
`-${options.resolution}.torrent`
try {
execSync(cmd + args)
} catch (err) {
console.error('Cannto exec command.', err)
process.exit(-1)
}
}

View File

@ -1,7 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
/* eslint-disable no-useless-escape */
import { registerTSPaths } from '../helpers/register-ts-paths' import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths() registerTSPaths()
@ -18,8 +16,6 @@ program
.command('upload', 'upload a video').alias('up') .command('upload', 'upload a video').alias('up')
.command('import-videos', 'import a video from a streaming platform').alias('import') .command('import-videos', 'import a video from a streaming platform').alias('import')
.command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token') .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token')
.command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
.command('repl', 'initiate a REPL to access internals')
.command('plugins [action]', 'manage instance plugins/themes').alias('p') .command('plugins [action]', 'manage instance plugins/themes').alias('p')
.command('redundancy [action]', 'manage instance redundancies').alias('r') .command('redundancy [action]', 'manage instance redundancies').alias('r')
@ -47,7 +43,7 @@ if (!process.argv.slice(2).length) {
/ / -" _/"/ / / -" _/"/
/ | ._\\\\ |\\ |_.".-" / / | ._\\\\ |\\ |_.".-" /
/ | __\\)|)|),/|_." _,." / | __\\)|)|),/|_." _,."
/ \_." " ") | ).-""---''-- / \\_." " ") | ).-""---''--
( "/.""7__-""'' ( "/.""7__-""''
| " ."._--._ | " ."._--._
\\ \\ (_ __ "" ".,_ \\ \\ (_ __ "" ".,_
@ -72,8 +68,7 @@ getSettings()
.addHelpText('after', '\n\n State: ' + state + '\n\n' + .addHelpText('after', '\n\n State: ' + state + '\n\n' +
' Examples:\n\n' + ' Examples:\n\n' +
' $ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' + ' $ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' +
' $ peertube up <videoFile>\n' + ' $ peertube up <videoFile>\n'
' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n'
) )
.parse(process.argv) .parse(process.argv)
}) })

View File

@ -1,102 +0,0 @@
import { program } from 'commander'
import { LiveVideoCreate, VideoPrivacy } from '@shared/models'
import {
createSingleServer,
killallServers,
sendRTMPStream,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel
} from '../../shared/extra-utils'
import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
type CommandType = 'live-mux' | 'live-transcoding'
registerTSPaths()
const command = program
.name('test-live')
.option('-t, --type <type>', 'live-muxing|live-transcoding')
.parse(process.argv)
run()
.catch(err => {
console.error(err)
process.exit(-1)
})
async function run () {
const commandType: CommandType = command['type']
if (!commandType) {
console.error('Miss command type')
process.exit(-1)
}
console.log('Starting server.')
const server = await createSingleServer(1, {}, { hideLogs: false, nodeArgs: [ '--inspect' ] })
const cleanup = async () => {
console.log('Killing server')
await killallServers([ server ])
}
process.on('exit', cleanup)
process.on('SIGINT', cleanup)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await buildConfig(server, commandType)
const attributes: LiveVideoCreate = {
name: 'live',
saveReplay: true,
channelId: server.store.channel.id,
privacy: VideoPrivacy.PUBLIC
}
console.log('Creating live.')
const { uuid: liveVideoUUID } = await server.live.create({ fields: attributes })
const live = await server.live.get({ videoId: liveVideoUUID })
console.log('Sending RTMP stream.')
const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
ffmpegCommand.on('error', err => {
console.error(err)
process.exit(-1)
})
ffmpegCommand.on('end', () => {
console.log('ffmpeg ended')
process.exit(0)
})
}
// ----------------------------------------------------------------------------
async function buildConfig (server: PeerTubeServer, commandType: CommandType) {
await server.config.updateCustomSubConfig({
newConfig: {
instance: {
customizations: {
javascript: '',
css: ''
}
},
live: {
enabled: true,
allowReplay: true,
transcoding: {
enabled: commandType === 'live-transcoding'
}
}
}
})
}

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,6 @@
- [CLI wrapper](#cli-wrapper) - [CLI wrapper](#cli-wrapper)
- [peertube-import-videos.js](#peertube-import-videosjs) - [peertube-import-videos.js](#peertube-import-videosjs)
- [peertube-upload.js](#peertube-uploadjs) - [peertube-upload.js](#peertube-uploadjs)
- [peertube-watch.js](#peertube-watchjs)
- [peertube-plugins.js](#peertube-pluginsjs) - [peertube-plugins.js](#peertube-pluginsjs)
- [peertube-redundancy.js](#peertube-redundancyjs) - [peertube-redundancy.js](#peertube-redundancyjs)
- [Server tools](#server-tools) - [Server tools](#server-tools)
@ -22,11 +21,6 @@
- [update-host.js](#update-hostjs) - [update-host.js](#update-hostjs)
- [reset-password.js](#reset-passwordjs) - [reset-password.js](#reset-passwordjs)
- [plugin install/uninstall](#plugin-installuninstall) - [plugin install/uninstall](#plugin-installuninstall)
- [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop)
- [.help](#help)
- [Lodash example](#lodash-example)
- [YoutubeDL example](#youtubedl-example)
- [Models examples](#models-examples)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -75,8 +69,6 @@ You can access it as `peertube` via an alias in your `.bashrc` like `alias peert
auth [action] register your accounts on remote instances to use them with other commands auth [action] register your accounts on remote instances to use them with other commands
upload|up upload a video upload|up upload a video
import-videos|import import a video from a streaming platform import-videos|import import a video from a streaming platform
watch|w watch a video in the terminal ✩°。⋆
repl initiate a REPL to access internals
plugins|p [action] manage instance plugins plugins|p [action] manage instance plugins
redundancy|r [action] manage video redundancies redundancy|r [action] manage video redundancies
help [cmd] display help for [cmd] help [cmd] display help for [cmd]
@ -100,12 +92,6 @@ You can now use that account to upload videos without feeding the same parameter
$ peertube up <videoFile> $ peertube up <videoFile>
``` ```
And now that your video is online, you can watch it from the confort of your terminal (use `peertube watch --help` to see the supported players):
```bash
$ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10
```
To list, install, uninstall dynamically plugins/themes of an instance: To list, install, uninstall dynamically plugins/themes of an instance:
```bash ```bash
@ -169,22 +155,6 @@ $ cd ${CLONE}
$ node dist/server/tools/peertube-upload.js --help $ node dist/server/tools/peertube-upload.js --help
``` ```
#### peertube-watch.js
You can use this script to play videos directly from the CLI.
It provides support for different players:
- ascii (default ; plays in ascii art in your terminal!)
- mpv
- mplayer
- vlc
- stdout
- xbmc
- airplay
- chromecast
#### peertube-plugins.js #### peertube-plugins.js
Install/update/uninstall or list local or NPM PeerTube plugins: Install/update/uninstall or list local or NPM PeerTube plugins:
@ -413,134 +383,3 @@ $ # Docker installation
$ cd /var/www/peertube-docker $ cd /var/www/peertube-docker
$ docker-compose exec -u peertube peertube npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin $ docker-compose exec -u peertube peertube npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin
``` ```
### REPL (Read Eval Print Loop)
If you want to interact with the application libraries and objects even when PeerTube is not running, there is a REPL for that.
usage: `node ./dist/server/tools/peertube-repl.js`
"The default evaluator will, by default, assign the result of the most recently evaluated expression to the special variable `_` (underscore). Explicitly setting `_` to a value will disable this behavior."
- type `.help` to list commands available in the repl, notice it starts with a dot
- type `.exit` to exit, note that you still have to press CTRL-C to actually exit, or press CTRL-C (3 times) without typing `.exit` to exit
- type `context` to list all available objects and libraries in the context, note: `Promise` is also available but it's not listed in the context, in case you need promises for something
- type `env` to see the loaded environment variables
- type `path` to access path library
- type `lodash` to access lodash library
- type `uuidv1` to access uuid/v1 library
- type `uuidv3` to access uuid/v3 library
- type `uuidv4` to access uuid/v4 library
- type `uuidv5` to access uuid/v5 library
- type `YoutubeDL` to access youtube-dl library
- type `cli` to access the cli helpers object
- type `logger` to access the logger; if you log to it, it will write to stdout and to the peertube.log file
- type `constants` to access the constants loaded by the server
- type `coreUtils` to access the core-utils helpers object
- type `ffmpegUtils` to access the ffmpeg-utils helpers object
- type `peertubeCryptoUtils` to access the peertube-crypto helpers object
- type `signupUtils` to access the signup helpers object
- type `utils` to access the utils helpers object
- type `YoutubeDLUtils` to access the youtube-dl helpers object
- type `sequelizeTypescript` to access sequelizeTypescript
- type `modelsUtils` to access the models/utils
- type `models` to access the shortcut to sequelizeTypescript.models
- type `transaction` to access the shortcut to sequelizeTypescript.transaction
- type `query` to access the shortcut to sequelizeTypescript.query
- type `queryInterface` to access the shortcut to sequelizeTypescript.queryInterface
#### .help
```
PeerTube [1.0.0] (b10eb595)> .help
.break Sometimes you get stuck, this gets you out
.clear Break, and also clear the local context
.editor Enter editor mode
.exit Exit the repl
.help Print this help message
.load Load JS from a file into the REPL session
.r Reset REPL
.reset Reset REPL
.save Save all evaluated commands in this REPL session to a file
PeerTube [1.0.0] (b10eb595)>
```
#### Lodash example
```
PeerTube [1.0.0] (b10eb595)> lodash.keys(context)
[ 'global',
'console',
'DTRACE_NET_SERVER_CONNECTION',
'DTRACE_NET_STREAM_END',
'DTRACE_HTTP_SERVER_REQUEST',
'DTRACE_HTTP_SERVER_RESPONSE',
'DTRACE_HTTP_CLIENT_REQUEST',
'DTRACE_HTTP_CLIENT_RESPONSE',
'process',
'Buffer',
'clearImmediate',
'clearInterval',
'clearTimeout',
'setImmediate',
'setInterval',
'setTimeout',
'XMLHttpRequest',
'compact2string',
'module',
'require',
'path',
'repl',
'context',
'env',
'lodash',
'uuidv1',
'uuidv3',
'uuidv4',
'uuidv5',
'cli',
'logger',
'constants',
'Sequelize',
'sequelizeTypescript',
'modelsUtils',
'models',
'transaction',
'query',
'queryInterface',
'YoutubeDL',
'coreUtils',
'ffmpegUtils',
'peertubeCryptoUtils',
'signupUtils',
'utils',
'YoutubeDLUtils' ]
PeerTube [1.0.0] (b10eb595)>
```
#### YoutubeDL example
```
YoutubeDL.getInfo('https://www.youtube.com/watch?v=I5ZN289jjDo', function(err, data) {console.log(err, data)})
```
#### Models examples
```
PeerTube [1.0.0] (b10eb595)> new models.ActorModel({id: 3}).getVideoChannel().then(function(data){console.log(data.dataValues.name)})
Promise {
_bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined }
PeerTube [1.0.0] (b10eb595)> Main root channel
PeerTube [1.0.0] (b10eb595)> let out; new models.UserModel({id: 1}).getAccount().then(function (data) {out = data.dataValues.id})
Promise {
_bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined }
PeerTube [1.0.0] (b10eb595)> out
2
PeerTube [1.0.0] (b10eb595)>
```

View File

@ -5967,16 +5967,6 @@ markdown-it@^12.0.4:
mdurl "^1.0.1" mdurl "^1.0.1"
uc.micro "^1.0.5" uc.micro "^1.0.5"
marked-man@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/marked-man/-/marked-man-0.7.0.tgz#220ba01d275d16f1a98e4e7fc3c5eac0630c68e4"
integrity sha512-zxK5E4jbuARALc+fIUAanM2njVGnrd9YvKrqoDHUg2XwNLJijo39EzMIg59LecHBHsIHNtPqepqnJp4SmL/EVg==
marked@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/marked/-/marked-3.0.2.tgz#60ce97d6aec34dd882ab4bb4df82494666854e17"
integrity sha512-TMJQQ79Z0e3rJYazY0tIoMsFzteUGw9fB3FD+gzuIT3zLuG9L9ckIvUfF51apdJkcqc208jJN2KbtPbOvXtbjA==
math-interval-parser@^2.0.1: math-interval-parser@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/math-interval-parser/-/math-interval-parser-2.0.1.tgz#e22cd6d15a0a7f4c03aec560db76513da615bed4" resolved "https://registry.yarnpkg.com/math-interval-parser/-/math-interval-parser-2.0.1.tgz#e22cd6d15a0a7f4c03aec560db76513da615bed4"