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",
"video.js": "^7",
"videojs-contextmenu-pt": "^5.4.1",
"videojs-contrib-quality-levels": "^2.0.9",
"videojs-dock": "^2.0.2",
"videojs-hotkeys": "^0.2.27",
"videostream": "~3.2.1",

View File

@ -1,9 +1,9 @@
// 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
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 { HlsjsConfigHandlerOptions, QualityLevelRepresentation, QualityLevels, VideoJSTechHLS } from '../peertube-videojs-typings'
import { HlsjsConfigHandlerOptions, PeerTubeResolution, VideoJSTechHLS } from '../peertube-videojs-typings'
type ErrorCounts = {
[ type: string ]: number
@ -102,15 +102,10 @@ class Html5Hlsjs {
private dvrDuration: number = null
private edgeMargin: number = null
private handlers: { [ id in 'play' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = {
play: null,
playing: null,
textTracksChange: null,
audioTracksChange: null
private handlers: { [ id in 'play' ]: EventListener } = {
play: null
}
private uiTextTrackHandled = false
constructor (vjs: typeof videojs, source: videojs.Tech.SourceObject, tech: videojs.Tech) {
this.vjs = vjs
this.source = source
@ -177,10 +172,6 @@ class Html5Hlsjs {
// See comment for `initialize` method.
dispose () {
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()
}
@ -281,11 +272,7 @@ class Html5Hlsjs {
}
}
private switchQuality (qualityId: number) {
this.hls.currentLevel = qualityId
}
private _levelLabel (level: Level) {
private buildLevelLabel (level: Level) {
if (this.player.srOptions_.levelLabelHandler) {
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.bitrate) return (level.bitrate / 1000) + 'kbps'
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)
}
return '0'
}
private _notifyVideoQualities () {
if (!this.metadata) return
const cleanTracklist = []
if (this.metadata.levels.length > 1) {
const autoLevel = {
id: -1,
label: 'auto',
selected: this.hls.manualLevel === -1
}
cleanTracklist.push(autoLevel)
}
const resolutions: PeerTubeResolution[] = []
this.metadata.levels.forEach((level, index) => {
// Don't write in level (shared reference with Hls.js)
const quality = {
resolutions.push({
id: index,
selected: index === this.hls.manualLevel,
label: this._levelLabel(level)
}
height: level.height,
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 = {
qualityData: { video: cleanTracklist },
qualitySwitchCallback: this.switchQuality.bind(this)
}
resolutions.push({
id: -1,
label: this.player.localize('Auto'),
selected: true,
selectCallback: () => this.hls.currentLevel = -1
})
this.tech.trigger('loadedqualitydata', payload)
// 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'
}
}
this.player.peertubeResolutions().add(resolutions)
}
private _startLoad () {
@ -472,97 +329,10 @@ class Html5Hlsjs {
return result
}
private _filterDisplayableTextTracks (textTracks: TextTrackList) {
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) {
private _onMetaData (_event: any, data: ManifestParsedData) {
// This could arrive before 'loadedqualitydata' handlers is registered, remember it so we can raise it later
this.metadata = data as any
this._handleQualityLevels()
}
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, ''))
}
}
}
}
this.metadata = data
this._notifyVideoQualities()
}
private _initHlsjs () {
@ -577,11 +347,6 @@ class Html5Hlsjs {
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
// That's why we have a separate if block here to set the 'play' listener
if (this.hlsjsConfig.autoStartLoad === false) {
@ -589,17 +354,12 @@ class Html5Hlsjs {
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._executeHooksFor('beforeinitialize')
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 as any)) // FIXME: typings
this.hls.on(Hlsjs.Events.MANIFEST_PARSED, (event, data) => this._onMetaData(event, data))
this.hls.on(Hlsjs.Events.LEVEL_LOADED, (event, data) => {
// The DVR plugin will auto seek to "live edge" on start up
if (this.hlsjsConfig.liveSyncDuration) {
@ -612,12 +372,25 @@ class Html5Hlsjs {
this.dvrDuration = data.details.totalduration
this._duration = this.isLive ? Infinity : data.details.totalduration
})
this.hls.once(Hlsjs.Events.FRAG_LOADED, () => {
// Emit custom 'loadedmetadata' event for parity with `videojs-contrib-hls`
// Ref: https://github.com/videojs/videojs-contrib-hls#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.loadSource(this.source.src)

View File

@ -116,14 +116,6 @@ class P2pMediaLoaderPlugin extends Plugin {
const options = this.player.tech(true).options_ as any
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) => {
console.error('Segment error.', segment, err)

View File

@ -1,13 +1,13 @@
import 'videojs-hotkeys/videojs.hotkeys'
import 'videojs-dock'
import 'videojs-contextmenu-pt'
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 './peertube-resolutions-plugin'
import './videojs-components/next-previous-video-button'
import './videojs-components/p2p-info-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 { timeToInt } from '@shared/core-utils'
import {
@ -10,7 +9,7 @@ import {
saveVideoWatchHistory,
saveVolumeInStore
} 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'
const Plugin = videojs.getPlugin('plugin')
@ -27,7 +26,6 @@ class PeerTubePlugin extends Plugin {
private videoViewInterval: any
private userWatchingVideoInterval: any
private lastResolutionChange: ResolutionUpdateData
private isLive: boolean
@ -54,22 +52,6 @@ class PeerTubePlugin extends Plugin {
this.player.ready(() => {
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()
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 => {
return t.kind === 'captions' && t.mode === 'showing'
})
@ -216,22 +198,6 @@ class PeerTubePlugin extends Plugin {
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 () {
this.player.controlBar.on('mouseenter', () => {
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 videojs from 'video.js'
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 { PlayerMode } from './peertube-player-manager'
import { PeerTubePlugin } from './peertube-plugin'
import { PeerTubeResolutionsPlugin } from './peertube-resolutions-plugin'
import { PlaylistPlugin } from './playlist/playlist-plugin'
import { EndCardOptions } from './upnext/end-card'
import { StatsCardOptions } from './stats/stats-card'
import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
import { StatsForNerdsPlugin } from './stats/stats-plugin'
import { EndCardOptions } from './upnext/end-card'
import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
declare module 'video.js' {
@ -37,16 +35,15 @@ declare module 'video.js' {
p2pMediaLoader (): P2pMediaLoaderPlugin
peertubeResolutions (): PeerTubeResolutionsPlugin
contextmenuUI (options: any): any
bezels (): void
stats (options?: StatsCardOptions): StatsForNerdsPlugin
qualityLevels (): QualityLevels
textTracks (): TextTrackList & {
on: Function
tracks_: (TextTrack & { id: string, label: string, src: string })[]
}
@ -69,24 +66,16 @@ export interface HlsjsConfigHandlerOptions {
levelLabelHandler?: (level: Level) => string
}
type QualityLevelRepresentation = {
type PeerTubeResolution = {
id: number
height: number
height?: number
label?: string
width?: number
bandwidth?: number
bitrate?: number
enabled?: Function
_enabled: boolean
}
type QualityLevels = QualityLevelRepresentation[] & {
selectedIndex: number
selectedIndex_: number
addQualityLevel (representation: QualityLevelRepresentation): void
selected: boolean
selectCallback: () => void
}
type VideoJSCaption = {
@ -131,7 +120,7 @@ type PlaylistPluginOptions = {
type NextPreviousVideoButtonOptions = {
type: 'next' | 'previous'
handler: Function
handler: () => void
isDisabled: () => boolean
}
@ -214,7 +203,7 @@ type PlayerNetworkInfo = {
type PlaylistItemOptions = {
element: VideoPlaylistElement
onClicked: Function
onClicked: () => void
}
export {
@ -229,9 +218,8 @@ export {
PeerTubePluginOptions,
WebtorrentPluginOptions,
P2PMediaLoaderPluginOptions,
PeerTubeResolution,
VideoJSPluginOptions,
LoadedQualityData,
QualityLevelRepresentation,
PeerTubeLinkButtonOptions,
QualityLevels
PeerTubeLinkButtonOptions
}

View File

@ -1,6 +1,4 @@
import videojs from 'video.js'
import { LoadedQualityData } from '../peertube-videojs-typings'
import { ResolutionMenuItem } from './resolution-menu-item'
const Menu = videojs.getComponent('Menu')
@ -13,9 +11,12 @@ class ResolutionMenuButton extends MenuButton {
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 () {
@ -58,20 +59,8 @@ class ResolutionMenuButton extends MenuButton {
})
}
private buildQualities (data: LoadedQualityData) {
// The automatic resolution item will need other labels
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
private buildQualities () {
for (const d of this.player().peertubeResolutions().getResolutions()) {
const label = d.label === '0p'
? this.player().localize('Audio-only')
: d.label
@ -81,25 +70,11 @@ class ResolutionMenuButton extends MenuButton {
{
id: d.id,
label,
selected: d.selected,
callback: data.qualitySwitchCallback
selected: d.selected
})
)
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()) {
this.addClickListener(m)
}

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import {
getStoredVolume,
saveAverageBandwidth
} 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 { PeertubeChunkStore } from './peertube-chunk-store'
import { renderVideo } from './video-renderer'
@ -175,11 +175,10 @@ class WebTorrentPlugin extends Plugin {
return done()
})
this.changeQuality()
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id })
this.selectAppropriateResolution(true)
}
updateResolution (resolutionId: number, delay = 0) {
updateEngineResolution (resolutionId: number, delay = 0) {
// Remember player state
const currentTime = this.player.currentTime()
const isPaused = this.player.paused()
@ -219,17 +218,10 @@ class WebTorrentPlugin extends Plugin {
}
}
enableAutoResolution () {
this.autoResolution = true
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
}
disableAutoResolution (forbid = false) {
if (forbid === true) this.autoResolutionPossible = false
disableAutoResolution () {
this.autoResolution = false
this.trigger('autoResolutionChange', { possible: this.autoResolutionPossible })
this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
this.autoResolutionPossible = false
this.player.peertubeResolutions().disableAutoResolution()
}
isAutoResolutionPossible () {
@ -244,6 +236,22 @@ class WebTorrentPlugin extends Plugin {
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 (
magnetOrTorrentUrl: string,
previousVideoFile: VideoFile,
@ -466,7 +474,7 @@ class WebTorrentPlugin extends Plugin {
}
if (changeResolution === true) {
this.updateResolution(file.resolution.id, changeResolutionDelay)
this.updateEngineResolution(file.resolution.id, changeResolutionDelay)
// Wait some seconds in observation of our new resolution
this.isAutoResolutionObservation = true
@ -516,7 +524,7 @@ class WebTorrentPlugin extends Plugin {
private fallbackToHttp (options: PlayOptions, done?: (err?: Error) => void) {
const paused = this.player.paused()
this.disableAutoResolution(true)
this.disableAutoResolution()
this.flushVideoFile(this.currentVideoFile, true)
this.torrent = null
@ -528,7 +536,7 @@ class WebTorrentPlugin extends Plugin {
this.player.src = this.savePlayerSrcFunction
this.player.src(httpUrl)
this.changeQuality()
this.selectAppropriateResolution(true)
// We changed the source, so reinit captions
this.player.trigger('sourcechange')
@ -601,32 +609,22 @@ class WebTorrentPlugin extends Plugin {
}
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) {
const representation = {
id: file.resolution.id,
label: this.buildQualityLabel(file),
height: file.resolution.id,
_enabled: true
}
resolutions.push({
id: -1,
label: this.player.localize('Auto'),
selected: true,
selectCallback: () => this.changeQuality(-1)
})
this.player.qualityLevels().addQualityLevel(representation)
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)
this.player.peertubeResolutions().add(resolutions)
}
private buildQualityLabel (file: VideoFile) {
@ -639,29 +637,16 @@ class WebTorrentPlugin extends Plugin {
return label
}
private qualitySwitchCallback (id: number) {
if (id === -1) {
if (this.autoResolutionPossible === true) this.enableAutoResolution()
return
}
private selectAppropriateResolution (byEngine: boolean) {
const resolution = this.autoResolution
? -1
: this.getCurrentResolutionId()
this.disableAutoResolution()
this.updateResolution(id)
}
const autoResolutionChosen = this.autoResolution
? this.getCurrentResolutionId()
: undefined
private changeQuality () {
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
}
this.player.peertubeResolutions().select({ id: resolution, autoResolutionChosenId: autoResolutionChosen, byEngine })
}
}

View File

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

View File

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

View File

@ -12618,14 +12618,6 @@ videojs-contextmenu-pt@^5.4.1:
global "^4.4.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:
version "2.2.0"
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",
"fast-xml-parser": "^3.19.0",
"maildev": "^1.0.0-rc3",
"marked": "^3.0.2",
"marked-man": "^0.7.0",
"mocha": "^9.0.0",
"nodemon": "^2.0.1",
"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
--------
```
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).
See https://docs.joinpeertube.org/maintain-tools?id=remote-tools

View File

@ -5,8 +5,7 @@
"dependencies": {
"application-config": "^2.0.0",
"cli-table3": "^0.6.0",
"netrc-parser": "^3.1.6",
"webtorrent-hybrid": "^4.0.3"
"netrc-parser": "^3.1.6"
},
"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
/* eslint-disable no-useless-escape */
import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
@ -18,8 +16,6 @@ program
.command('upload', 'upload a video').alias('up')
.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('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('redundancy [action]', 'manage instance redundancies').alias('r')
@ -47,7 +43,7 @@ if (!process.argv.slice(2).length) {
/ / -" _/"/
/ | ._\\\\ |\\ |_.".-" /
/ | __\\)|)|),/|_." _,."
/ \_." " ") | ).-""---''--
/ \\_." " ") | ).-""---''--
( "/.""7__-""''
| " ."._--._
\\ \\ (_ __ "" ".,_
@ -72,8 +68,7 @@ getSettings()
.addHelpText('after', '\n\n State: ' + state + '\n\n' +
' Examples:\n\n' +
' $ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' +
' $ peertube up <videoFile>\n' +
' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n'
' $ peertube up <videoFile>\n'
)
.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)
- [peertube-import-videos.js](#peertube-import-videosjs)
- [peertube-upload.js](#peertube-uploadjs)
- [peertube-watch.js](#peertube-watchjs)
- [peertube-plugins.js](#peertube-pluginsjs)
- [peertube-redundancy.js](#peertube-redundancyjs)
- [Server tools](#server-tools)
@ -22,11 +21,6 @@
- [update-host.js](#update-hostjs)
- [reset-password.js](#reset-passwordjs)
- [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 -->
@ -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
upload|up upload a video
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
redundancy|r [action] manage video redundancies
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>
```
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:
```bash
@ -169,22 +155,6 @@ $ cd ${CLONE}
$ 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
Install/update/uninstall or list local or NPM PeerTube plugins:
@ -413,134 +383,3 @@ $ # Docker installation
$ cd /var/www/peertube-docker
$ 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"
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:
version "2.0.1"
resolved "https://registry.yarnpkg.com/math-interval-parser/-/math-interval-parser-2.0.1.tgz#e22cd6d15a0a7f4c03aec560db76513da615bed4"