Upgrade to angular 18 & vite

This commit is contained in:
Chocobozzz 2024-06-24 10:15:25 +02:00
parent ec33467261
commit 9772280e99
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
56 changed files with 4040 additions and 6319 deletions

View File

@ -192,7 +192,7 @@ npm run dev
### Embed ### Embed
The embed is a standalone application built using Webpack. The embed is a standalone application built using Vite.
The generated files (HTML entrypoint and multiple JS and CSS files) are served by the PeerTube server (behind `localhost:9000/videos/embed/:videoUUID` or `localhost:9000/video-playlists/embed/:playlistUUID`). The generated files (HTML entrypoint and multiple JS and CSS files) are served by the PeerTube server (behind `localhost:9000/videos/embed/:videoUUID` or `localhost:9000/video-playlists/embed/:playlistUUID`).
The following command will compile embed files and run the PeerTube server: The following command will compile embed files and run the PeerTube server:

View File

@ -28,7 +28,6 @@
"lines-between-class-members": "off", "lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": [ "off" ], "@typescript-eslint/lines-between-class-members": [ "off" ],
"arrow-body-style": "off", "arrow-body-style": "off",
"import/no-webpack-loader-syntax": "off",
"no-underscore-dangle": "off", "no-underscore-dangle": "off",
"n/no-callback-literal": "off", "n/no-callback-literal": "off",
"@angular-eslint/component-selector": [ "@angular-eslint/component-selector": [

View File

@ -161,18 +161,23 @@
}, },
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular/build:application",
"options": { "options": {
"i18nMissingTranslation": "ignore",
"localize": true, "localize": true,
"outputPath": "dist", "outputPath": {
"base": "dist"
},
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "tsconfig.json", "tsConfig": "tsconfig.json",
"polyfills": "src/polyfills.ts", "polyfills": [
"src/polyfills.ts"
],
"baseHref": "/", "baseHref": "/",
"stylePreprocessorOptions": { "stylePreprocessorOptions": {
"includePaths": [ "includePaths": [
"src/sass/include" "src/sass/include",
"."
] ]
}, },
"assets": [ "assets": [
@ -209,12 +214,14 @@
"@formatjs/intl-pluralrules/should-polyfill" "@formatjs/intl-pluralrules/should-polyfill"
], ],
"scripts": [], "scripts": [],
"vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true, "sourceMap": true,
"optimization": false, "optimization": false,
"namedChunks": true "namedChunks": true,
"browser": "src/main.ts",
"loader": {
".svg": "text"
}
}, },
"configurations": { "configurations": {
"production": { "production": {
@ -223,10 +230,7 @@
"sourceMap": false, "sourceMap": false,
"namedChunks": false, "namedChunks": false,
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false, "serviceWorker": "src/ngsw-config.json",
"buildOptimizer": true,
"serviceWorker": true,
"ngswConfigPath": "src/ngsw-config.json",
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
@ -281,7 +285,7 @@
} }
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular/build:dev-server",
"options": { "options": {
"proxyConfig": "proxy.config.json", "proxyConfig": "proxy.config.json",
"buildTarget": "PeerTube:build" "buildTarget": "PeerTube:build"
@ -299,7 +303,7 @@
} }
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular/build:extract-i18n",
"options": { "options": {
"buildTarget": "PeerTube:build" "buildTarget": "PeerTube:build"
} }

View File

@ -16,40 +16,42 @@
"lint": "npm run lint-ts && npm run lint-scss", "lint": "npm run lint-ts && npm run lint-scss",
"lint-ts": "eslint --cache --ext .ts src/standalone/**/*.ts && npm run ng lint", "lint-ts": "eslint --cache --ext .ts src/standalone/**/*.ts && npm run ng lint",
"lint-scss": "stylelint 'src/**/*.scss'", "lint-scss": "stylelint 'src/**/*.scss'",
"webpack": "webpack",
"eslint": "eslint", "eslint": "eslint",
"ng": "ng", "ng": "ng",
"webpack-bundle-analyzer": "webpack-bundle-analyzer",
"webdriver-manager": "webdriver-manager", "webdriver-manager": "webdriver-manager",
"ngx-extractor": "ngx-extractor", "ngx-extractor": "ngx-extractor",
"stylelint": "stylelint" "stylelint": "stylelint"
}, },
"browser": {
"net": false,
"stream": false,
"os": false,
"util": false
},
"workspaces": [ "workspaces": [
"../packages/*" "../packages/*"
], ],
"typings": "*.d.ts", "typings": "*.d.ts",
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^17.0.9", "@angular-eslint/builder": "^18.0.1",
"@angular-eslint/builder": "^17.1.1", "@angular-eslint/eslint-plugin": "^18.0.1",
"@angular-eslint/eslint-plugin": "^17.1.1", "@angular-eslint/eslint-plugin-template": "^18.0.1",
"@angular-eslint/eslint-plugin-template": "^17.1.1", "@angular-eslint/schematics": "^18.0.1",
"@angular-eslint/schematics": "^17.1.1", "@angular-eslint/template-parser": "^18.0.1",
"@angular-eslint/template-parser": "^17.1.1", "@angular/animations": "^18.0.4",
"@angular/animations": "^17.0.8", "@angular/build": "^18.0.5",
"@angular/cdk": "^17.0.4", "@angular/cdk": "^18.0.4",
"@angular/cli": "^17.0.9", "@angular/cli": "^18.0.5",
"@angular/common": "^17.0.8", "@angular/common": "^18.0.4",
"@angular/compiler": "^17.0.8", "@angular/compiler": "^18.0.4",
"@angular/compiler-cli": "^17.0.8", "@angular/compiler-cli": "^18.0.4",
"@angular/core": "^17.0.8", "@angular/core": "^18.0.4",
"@angular/forms": "^17.0.8", "@angular/forms": "^18.0.4",
"@angular/localize": "^17.0.8", "@angular/localize": "^18.0.4",
"@angular/platform-browser": "^17.0.8", "@angular/platform-browser": "^18.0.4",
"@angular/platform-browser-dynamic": "^17.0.8", "@angular/platform-browser-dynamic": "^18.0.4",
"@angular/router": "^17.0.8", "@angular/router": "^18.0.4",
"@angular/service-worker": "^17.0.8", "@angular/service-worker": "^18.0.4",
"@babel/core": "^7.18.5",
"@babel/preset-env": "^7.18.2",
"@formatjs/intl-locale": "^4.0.0", "@formatjs/intl-locale": "^4.0.0",
"@formatjs/intl-pluralrules": "^5.2.2", "@formatjs/intl-pluralrules": "^5.2.2",
"@ng-bootstrap/ng-bootstrap": "^17.0.0", "@ng-bootstrap/ng-bootstrap": "^17.0.0",
@ -58,11 +60,8 @@
"@ngx-loading-bar/http-client": "^6.0.0", "@ngx-loading-bar/http-client": "^6.0.0",
"@ngx-loading-bar/router": "^6.0.0", "@ngx-loading-bar/router": "^6.0.0",
"@peertube/maildev": "^1.2.0", "@peertube/maildev": "^1.2.0",
"@peertube/p2p-media-loader-core": "^1.0.15", "@peertube/p2p-media-loader-core": "^1.0.19",
"@peertube/p2p-media-loader-hlsjs": "^1.0.15", "@peertube/p2p-media-loader-hlsjs": "^1.0.19",
"@peertube/peertube-core-utils": "*",
"@peertube/peertube-models": "*",
"@peertube/videojs-contextmenu": "^5.5.0",
"@peertube/xliffmerge": "^2.0.3", "@peertube/xliffmerge": "^2.0.3",
"@popperjs/core": "^2.11.5", "@popperjs/core": "^2.11.5",
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
@ -86,22 +85,18 @@
"@wdio/shared-store-service": "^8.10.5", "@wdio/shared-store-service": "^8.10.5",
"@wdio/spec-reporter": "^8.10.5", "@wdio/spec-reporter": "^8.10.5",
"angularx-qrcode": "17.0.1", "angularx-qrcode": "17.0.1",
"babel-loader": "^9.1.0",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"chart.js": "^4.3.0", "chart.js": "^4.3.0",
"chartjs-plugin-zoom": "~2.0.1", "chartjs-plugin-zoom": "~2.0.1",
"core-js": "^3.22.8", "core-js": "^3.22.8",
"css-loader": "^7.1.2",
"debug": "^4.3.1", "debug": "^4.3.1",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "^48.1.0", "eslint-plugin-jsdoc": "^48.1.0",
"eslint-plugin-prefer-arrow": "latest", "eslint-plugin-prefer-arrow": "latest",
"expect-webdriverio": "^4.2.3", "expect-webdriverio": "^4.2.3",
"hls.js": "~1.3", "hls.js": "~1.5.11",
"html-loader": "^5.0.0",
"html-webpack-plugin": "^5.3.1",
"intl-messageformat": "^10.1.0", "intl-messageformat": "^10.1.0",
"jschannel": "^1.0.2", "jschannel": "^1.0.2",
"linkify-html": "^4.0.2", "linkify-html": "^4.0.2",
@ -109,29 +104,21 @@
"lodash-es": "^4.17.4", "lodash-es": "^4.17.4",
"markdown-it": "14.1.0", "markdown-it": "14.1.0",
"markdown-it-emoji": "^3.0.0", "markdown-it-emoji": "^3.0.0",
"mini-css-extract-plugin": "^2.2.0",
"ngx-uploadx": "^6.1.0", "ngx-uploadx": "^6.1.0",
"path-browserify": "^1.0.0",
"postcss": "^8.4.14",
"primeng": "^17.3.1", "primeng": "^17.3.1",
"raw-loader": "^4.0.2",
"rxjs": "^7.3.0", "rxjs": "^7.3.0",
"sanitize-html": "^2.1.2", "sanitize-html": "^2.1.2",
"sass": "^1.58.1",
"sass-loader": "^14.1.1",
"sha.js": "^2.4.11", "sha.js": "^2.4.11",
"socket.io-client": "^4.5.4", "socket.io-client": "^4.5.4",
"stylelint": "^16.2.1", "stylelint": "^16.2.1",
"stylelint-config-sass-guidelines": "^11.0.0", "stylelint-config-sass-guidelines": "^11.0.0",
"tinykeys": "^2.1.0", "tinykeys": "^2.1.0",
"ts-loader": "^9.3.0",
"ts-node": "^10.9.1",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"typescript": "~5.2", "typescript": "~5.4.5",
"video.js": "^7.19.2", "video.js": "^7.19.2",
"webpack": "^5.73.0", "vite": "^5.3.1",
"webpack-bundle-analyzer": "^4.4.2", "vite-plugin-checker": "^0.6.4",
"webpack-cli": "^5.0.1", "vite-plugin-node-polyfills": "^0.22.0",
"zone.js": "~0.14.2" "zone.js": "~0.14.2"
}, },
"dependencies": {} "dependencies": {}

View File

@ -31,15 +31,5 @@
"/client/locales": { "/client/locales": {
"target": "http://127.0.0.1:9000", "target": "http://127.0.0.1:9000",
"secure": false "secure": false
},
"/!(client)**": {
"target": "http://127.0.0.1:3000/client/index.html",
"secure": false,
"logLevel": "debug"
},
"/!(client)**/**": {
"target": "http://127.0.0.1:3000/client/index.html",
"secure": false,
"logLevel": "debug"
} }
} }

View File

@ -2,11 +2,11 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser' import { DomSanitizer } from '@angular/platform-browser'
const images = { const images = {
about: require('!!raw-loader?!../../../assets/images/mascot/register/about.svg').default, about: require('../../../assets/images/mascot/register/about.svg'),
terms: require('!!raw-loader?!../../../assets/images/mascot/register/terms.svg').default, terms: require('../../../assets/images/mascot/register/terms.svg'),
success: require('!!raw-loader?!../../../assets/images/mascot/register/success.svg').default, success: require('../../../assets/images/mascot/register/success.svg'),
channel: require('!!raw-loader?!../../../assets/images/mascot/register/channel.svg').default, channel: require('../../../assets/images/mascot/register/channel.svg'),
account: require('!!raw-loader?!../../../assets/images/mascot/register/account.svg').default account: require('../../../assets/images/mascot/register/account.svg')
} }
export type MascotImageName = keyof typeof images export type MascotImageName = keyof typeof images

View File

@ -276,7 +276,7 @@ export class AppComponent implements OnInit, AfterViewInit {
if (this.serverConfig.instance.customizations.javascript) { if (this.serverConfig.instance.customizations.javascript) {
try { try {
/* eslint-disable no-eval */ /* eslint-disable no-eval */
eval(this.serverConfig.instance.customizations.javascript) window.eval(this.serverConfig.instance.customizations.javascript)
} catch (err) { } catch (err) {
logger.error('Cannot eval custom JavaScript.', err) logger.error('Cannot eval custom JavaScript.', err)
} }

View File

@ -1,4 +1,4 @@
import * as MarkdownIt from 'markdown-it' import MarkdownIt from 'markdown-it'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { import {
buildVideoLink, buildVideoLink,

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { HttpParams } from '@angular/common/http' import { HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ConfirmService } from '@app/core/confirm' import { ConfirmService } from '@app/core/confirm'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { NavigationCancel, NavigationEnd, Router } from '@angular/router' import { NavigationCancel, NavigationEnd, Router } from '@angular/router'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { pairwise } from 'rxjs' import { pairwise } from 'rxjs'
import { ViewportScroller } from '@angular/common' import { ViewportScroller } from '@angular/common'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'

View File

@ -23,7 +23,7 @@ import { LoginLinkComponent } from '@app/shared/shared-main/angular/login-link.c
import { PeertubeModalService } from '@app/shared/shared-main/peertube-modal/peertube-modal.service' import { PeertubeModalService } from '@app/shared/shared-main/peertube-modal/peertube-modal.service'
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { HTMLServerConfig, ServerConfig, UserRight, UserRightType, VideoConstant } from '@peertube/peertube-models' import { HTMLServerConfig, ServerConfig, UserRight, UserRightType, VideoConstant } from '@peertube/peertube-models'
import * as debug from 'debug' import debug from 'debug'
import { forkJoin, Subscription } from 'rxjs' import { forkJoin, Subscription } from 'rxjs'
import { first, switchMap } from 'rxjs/operators' import { first, switchMap } from 'rxjs/operators'
import { LanguageChooserComponent } from './language-chooser.component' import { LanguageChooserComponent } from './language-chooser.component'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { SortMeta, SharedModule } from 'primeng/api' import { SortMeta, SharedModule } from 'primeng/api'
import { Component, Input, OnInit, ViewChild } from '@angular/core' import { Component, Input, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { debounceTime, distinctUntilChanged } from 'rxjs/operators' import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'

View File

@ -3,82 +3,82 @@ import { HooksService } from '@app/core/plugins/hooks.service'
const icons = { const icons = {
// misc icons // misc icons
'npm': require('!!raw-loader?!../../../assets/images/misc/npm.svg').default, 'npm': require('../../../assets/images/misc/npm.svg'),
'markdown': require('!!raw-loader?!../../../assets/images/misc/markdown.svg').default, 'markdown': require('../../../assets/images/misc/markdown.svg'),
'language': require('!!raw-loader?!../../../assets/images/misc/language.svg').default, 'language': require('../../../assets/images/misc/language.svg'),
'video-lang': require('!!raw-loader?!../../../assets/images/misc/video-lang.svg').default, 'video-lang': require('../../../assets/images/misc/video-lang.svg'),
'support': require('!!raw-loader?!../../../assets/images/misc/support.svg').default, 'support': require('../../../assets/images/misc/support.svg'),
'peertube-x': require('!!raw-loader?!../../../assets/images/misc/peertube-x.svg').default, 'peertube-x': require('../../../assets/images/misc/peertube-x.svg'),
'robot': require('!!raw-loader?!../../../assets/images/misc/miscellaneous-services.svg').default, // material ui 'robot': require('../../../assets/images/misc/miscellaneous-services.svg'), // material ui
'videos': require('!!raw-loader?!../../../assets/images/misc/video-library.svg').default, // material ui 'videos': require('../../../assets/images/misc/video-library.svg'), // material ui
'history': require('!!raw-loader?!../../../assets/images/misc/history.svg').default, // material ui 'history': require('../../../assets/images/misc/history.svg'), // material ui
'subscriptions': require('!!raw-loader?!../../../assets/images/misc/subscriptions.svg').default, // material ui 'subscriptions': require('../../../assets/images/misc/subscriptions.svg'), // material ui
'playlist-add': require('!!raw-loader?!../../../assets/images/misc/playlist-add.svg').default, // material ui 'playlist-add': require('../../../assets/images/misc/playlist-add.svg'), // material ui
'follower': require('!!raw-loader?!../../../assets/images/misc/account-arrow-left.svg').default, // material ui 'follower': require('../../../assets/images/misc/account-arrow-left.svg'), // material ui
'following': require('!!raw-loader?!../../../assets/images/misc/account-arrow-right.svg').default, // material ui 'following': require('../../../assets/images/misc/account-arrow-right.svg'), // material ui
'tip': require('!!raw-loader?!../../../assets/images/misc/tip.svg').default, // material ui 'tip': require('../../../assets/images/misc/tip.svg'), // material ui
'flame': require('!!raw-loader?!../../../assets/images/misc/flame.svg').default, 'flame': require('../../../assets/images/misc/flame.svg'),
'local': require('!!raw-loader?!../../../assets/images/misc/local.svg').default, 'local': require('../../../assets/images/misc/local.svg'),
// feather icons // feather icons
'copy': require('!!raw-loader?!../../../assets/images/feather/copy.svg').default, 'copy': require('../../../assets/images/feather/copy.svg'),
'flag': require('!!raw-loader?!../../../assets/images/feather/flag.svg').default, 'flag': require('../../../assets/images/feather/flag.svg'),
'playlists': require('!!raw-loader?!../../../assets/images/feather/list.svg').default, 'playlists': require('../../../assets/images/feather/list.svg'),
'syndication': require('!!raw-loader?!../../../assets/images/feather/syndication.svg').default, 'syndication': require('../../../assets/images/feather/syndication.svg'),
'help': require('!!raw-loader?!../../../assets/images/feather/help.svg').default, 'help': require('../../../assets/images/feather/help.svg'),
'alert': require('!!raw-loader?!../../../assets/images/feather/alert.svg').default, 'alert': require('../../../assets/images/feather/alert.svg'),
'globe': require('!!raw-loader?!../../../assets/images/feather/globe.svg').default, 'globe': require('../../../assets/images/feather/globe.svg'),
'home': require('!!raw-loader?!../../../assets/images/feather/home.svg').default, 'home': require('../../../assets/images/feather/home.svg'),
'trending': require('!!raw-loader?!../../../assets/images/feather/trending.svg').default, 'trending': require('../../../assets/images/feather/trending.svg'),
'search': require('!!raw-loader?!../../../assets/images/feather/search.svg').default, 'search': require('../../../assets/images/feather/search.svg'),
'upload': require('!!raw-loader?!../../../assets/images/feather/upload.svg').default, 'upload': require('../../../assets/images/feather/upload.svg'),
'dislike': require('!!raw-loader?!../../../assets/images/feather/dislike.svg').default, 'dislike': require('../../../assets/images/feather/dislike.svg'),
'like': require('!!raw-loader?!../../../assets/images/feather/like.svg').default, 'like': require('../../../assets/images/feather/like.svg'),
'no': require('!!raw-loader?!../../../assets/images/feather/no.svg').default, 'no': require('../../../assets/images/feather/no.svg'),
'cloud-download': require('!!raw-loader?!../../../assets/images/feather/cloud-download.svg').default, 'cloud-download': require('../../../assets/images/feather/cloud-download.svg'),
'clock': require('!!raw-loader?!../../../assets/images/feather/clock.svg').default, 'clock': require('../../../assets/images/feather/clock.svg'),
'cog': require('!!raw-loader?!../../../assets/images/feather/cog.svg').default, 'cog': require('../../../assets/images/feather/cog.svg'),
'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default, 'delete': require('../../../assets/images/feather/delete.svg'),
'bell': require('!!raw-loader?!../../../assets/images/feather/bell.svg').default, 'bell': require('../../../assets/images/feather/bell.svg'),
'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default, 'sign-out': require('../../../assets/images/feather/log-out.svg'),
'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default, 'sign-in': require('../../../assets/images/feather/log-in.svg'),
'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default, 'download': require('../../../assets/images/feather/download.svg'),
'ownership-change': require('!!raw-loader?!../../../assets/images/feather/share.svg').default, 'ownership-change': require('../../../assets/images/feather/share.svg'),
'share': require('!!raw-loader?!../../../assets/images/feather/share-2.svg').default, 'share': require('../../../assets/images/feather/share-2.svg'),
'channel': require('!!raw-loader?!../../../assets/images/feather/tv.svg').default, 'channel': require('../../../assets/images/feather/tv.svg'),
'user': require('!!raw-loader?!../../../assets/images/feather/user.svg').default, 'user': require('../../../assets/images/feather/user.svg'),
'user-x': require('!!raw-loader?!../../../assets/images/feather/user-x.svg').default, 'user-x': require('../../../assets/images/feather/user-x.svg'),
'users': require('!!raw-loader?!../../../assets/images/feather/users.svg').default, 'users': require('../../../assets/images/feather/users.svg'),
'user-add': require('!!raw-loader?!../../../assets/images/feather/user-plus.svg').default, 'user-add': require('../../../assets/images/feather/user-plus.svg'),
'add': require('!!raw-loader?!../../../assets/images/feather/plus-circle.svg').default, 'add': require('../../../assets/images/feather/plus-circle.svg'),
'cloud-error': require('!!raw-loader?!../../../assets/images/feather/cloud-off.svg').default, 'cloud-error': require('../../../assets/images/feather/cloud-off.svg'),
'undo': require('!!raw-loader?!../../../assets/images/feather/corner-up-left.svg').default, 'undo': require('../../../assets/images/feather/corner-up-left.svg'),
'circle-tick': require('!!raw-loader?!../../../assets/images/feather/check-circle.svg').default, 'circle-tick': require('../../../assets/images/feather/check-circle.svg'),
'more-horizontal': require('!!raw-loader?!../../../assets/images/feather/more-horizontal.svg').default, 'more-horizontal': require('../../../assets/images/feather/more-horizontal.svg'),
'more-vertical': require('!!raw-loader?!../../../assets/images/feather/more-vertical.svg').default, 'more-vertical': require('../../../assets/images/feather/more-vertical.svg'),
'play': require('!!raw-loader?!../../../assets/images/feather/play.svg').default, 'play': require('../../../assets/images/feather/play.svg'),
'p2p': require('!!raw-loader?!../../../assets/images/feather/airplay.svg').default, 'p2p': require('../../../assets/images/feather/airplay.svg'),
'fullscreen': require('!!raw-loader?!../../../assets/images/feather/maximize.svg').default, 'fullscreen': require('../../../assets/images/feather/maximize.svg'),
'exit-fullscreen': require('!!raw-loader?!../../../assets/images/feather/minimize.svg').default, 'exit-fullscreen': require('../../../assets/images/feather/minimize.svg'),
'film': require('!!raw-loader?!../../../assets/images/feather/film.svg').default, 'film': require('../../../assets/images/feather/film.svg'),
'edit': require('!!raw-loader?!../../../assets/images/feather/edit-2.svg').default, 'edit': require('../../../assets/images/feather/edit-2.svg'),
'external-link': require('!!raw-loader?!../../../assets/images/feather/external-link.svg').default, 'external-link': require('../../../assets/images/feather/external-link.svg'),
'eye-open': require('!!raw-loader?!../../../assets/images/feather/eye.svg').default, 'eye-open': require('../../../assets/images/feather/eye.svg'),
'eye-close': require('!!raw-loader?!../../../assets/images/feather/eye-off.svg').default, 'eye-close': require('../../../assets/images/feather/eye-off.svg'),
'refresh': require('!!raw-loader?!../../../assets/images/feather/refresh-cw.svg').default, 'refresh': require('../../../assets/images/feather/refresh-cw.svg'),
'command': require('!!raw-loader?!../../../assets/images/feather/command.svg').default, 'command': require('../../../assets/images/feather/command.svg'),
'go': require('!!raw-loader?!../../../assets/images/feather/arrow-up-right.svg').default, 'go': require('../../../assets/images/feather/arrow-up-right.svg'),
'cross': require('!!raw-loader?!../../../assets/images/feather/x.svg').default, 'cross': require('../../../assets/images/feather/x.svg'),
'tick': require('!!raw-loader?!../../../assets/images/feather/check.svg').default, 'tick': require('../../../assets/images/feather/check.svg'),
'columns': require('!!raw-loader?!../../../assets/images/feather/columns.svg').default, 'columns': require('../../../assets/images/feather/columns.svg'),
'live': require('!!raw-loader?!../../../assets/images/feather/live.svg').default, 'live': require('../../../assets/images/feather/live.svg'),
'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default, 'repeat': require('../../../assets/images/feather/repeat.svg'),
'chevrons-up': require('!!raw-loader?!../../../assets/images/feather/chevrons-up.svg').default, 'chevrons-up': require('../../../assets/images/feather/chevrons-up.svg'),
'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default, 'message-circle': require('../../../assets/images/feather/message-circle.svg'),
'codesandbox': require('!!raw-loader?!../../../assets/images/feather/codesandbox.svg').default, 'codesandbox': require('../../../assets/images/feather/codesandbox.svg'),
'award': require('!!raw-loader?!../../../assets/images/feather/award.svg').default, 'award': require('../../../assets/images/feather/award.svg'),
'stats': require('!!raw-loader?!../../../assets/images/feather/stats.svg').default, 'stats': require('../../../assets/images/feather/stats.svg'),
'shield': require('!!raw-loader?!../../../assets/images/misc/shield.svg').default 'shield': require('../../../assets/images/misc/shield.svg')
} }
export type GlobalIconName = keyof typeof icons export type GlobalIconName = keyof typeof icons

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectorRef, ChangeDetectorRef,

View File

@ -15,7 +15,7 @@ import {
} from '@angular/core' } from '@angular/core'
import { ScreenService } from '@app/core' import { ScreenService } from '@app/core'
import { NgbDropdown, NgbModal, NgbDropdownAnchor, NgbDropdownMenu } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbModal, NgbDropdownAnchor, NgbDropdownMenu } from '@ng-bootstrap/ng-bootstrap'
import * as debug from 'debug' import debug from 'debug'
import { RouterLinkActive, RouterLink } from '@angular/router' import { RouterLinkActive, RouterLink } from '@angular/router'
import { NgFor, NgTemplateOutlet, NgIf, NgClass, SlicePipe } from '@angular/common' import { NgFor, NgTemplateOutlet, NgIf, NgClass, SlicePipe } from '@angular/common'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { Observable, Subject } from 'rxjs' import { Observable, Subject } from 'rxjs'
import { filter, first, map } from 'rxjs/operators' import { filter, first, map } from 'rxjs/operators'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs' import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
import { catchError, filter, map, switchMap, tap } from 'rxjs/operators' import { catchError, filter, map, switchMap, tap } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'

View File

@ -16,7 +16,7 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@peertube/peertube-core-utils' import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@peertube/peertube-core-utils'
import { ResultList, UserRight, VideoSortField } from '@peertube/peertube-models' import { ResultList, UserRight, VideoSortField } from '@peertube/peertube-models'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'
import * as debug from 'debug' import debug from 'debug'
import { Observable, Subject, Subscription, forkJoin, fromEvent, of } from 'rxjs' import { Observable, Subject, Subscription, forkJoin, fromEvent, of } from 'rxjs'
import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators' import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators'
import { InfiniteScrollerDirective } from '../shared-main/angular/infinite-scroller.directive' import { InfiniteScrollerDirective } from '../shared-main/angular/infinite-scroller.directive'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { Subject, Subscription } from 'rxjs' import { Subject, Subscription } from 'rxjs'
import { debounceTime, filter } from 'rxjs/operators' import { debounceTime, filter } from 'rxjs/operators'
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'

View File

@ -1,4 +1,4 @@
import * as debug from 'debug' import debug from 'debug'
import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs' import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
import { catchError, filter, map, share, switchMap, tap } from 'rxjs/operators' import { catchError, filter, map, share, switchMap, tap } from 'rxjs/operators'
import { HttpClient, HttpContext, HttpParams } from '@angular/common/http' import { HttpClient, HttpContext, HttpParams } from '@angular/common/http'

View File

@ -1,4 +1,4 @@
import '@peertube/videojs-contextmenu' import './shared/context-menu'
import './shared/upnext/end-card' import './shared/upnext/end-card'
import './shared/upnext/upnext-plugin' import './shared/upnext/upnext-plugin'
import './shared/stats/stats-card' import './shared/stats/stats-card'
@ -28,6 +28,9 @@ import './shared/mobile/peertube-mobile-plugin'
import './shared/mobile/peertube-mobile-buttons' import './shared/mobile/peertube-mobile-buttons'
import './shared/hotkeys/peertube-hotkeys-plugin' import './shared/hotkeys/peertube-hotkeys-plugin'
import './shared/metrics/metrics-plugin' import './shared/metrics/metrics-plugin'
import './shared/p2p-media-loader/hls-plugin'
import './shared/p2p-media-loader/p2p-media-loader-plugin'
import './shared/web-video/web-video-plugin'
import videojs, { VideoJsPlayer } from 'video.js' import videojs, { VideoJsPlayer } from 'video.js'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'
import { PluginsManager } from '@root-helpers/plugins-manager' import { PluginsManager } from '@root-helpers/plugins-manager'
@ -62,17 +65,10 @@ export class PeerTubePlayer {
private videojsDecodeErrors = 0 private videojsDecodeErrors = 0
private p2pMediaLoaderModule: any
private player: VideoJsPlayer private player: VideoJsPlayer
private currentLoadOptions: PeerTubePlayerLoadOptions private currentLoadOptions: PeerTubePlayerLoadOptions
private moduleLoaded = {
webVideo: false,
p2pMediaLoader: false
}
constructor (private options: PeerTubePlayerContructorOptions) { constructor (private options: PeerTubePlayerContructorOptions) {
this.pluginsManager = options.pluginsManager this.pluginsManager = options.pluginsManager
} }
@ -92,7 +88,6 @@ export class PeerTubePlayer {
this.disposeDynamicPluginsIfNeeded() this.disposeDynamicPluginsIfNeeded()
await this.lazyLoadModulesIfNeeded()
await this.buildPlayerIfNeeded() await this.buildPlayerIfNeeded()
if (this.currentLoadOptions.mode === 'p2p-media-loader') { if (this.currentLoadOptions.mode === 'p2p-media-loader') {
@ -169,7 +164,7 @@ export class PeerTubePlayer {
'liveOptions', 'liveOptions',
'hls' 'hls'
]) ])
}, this.p2pMediaLoaderModule) })
const { hlsjs, p2pMediaLoader } = await hlsOptionsBuilder.getPluginOptions() const { hlsjs, p2pMediaLoader } = await hlsOptionsBuilder.getPluginOptions()
@ -226,7 +221,7 @@ export class PeerTubePlayer {
saveAverageBandwidth(data.bandwidthEstimate) saveAverageBandwidth(data.bandwidthEstimate)
}) })
this.player.contextmenuUI(this.getContextMenuOptions()) this.player.contextMenu(this.getContextMenuOptions())
this.displayNotificationWhenOffline() this.displayNotificationWhenOffline()
}) })
@ -298,22 +293,6 @@ export class PeerTubePlayer {
} }
} }
private async lazyLoadModulesIfNeeded () {
if (this.currentLoadOptions.mode === 'web-video' && this.moduleLoaded.webVideo !== true) {
await import('./shared/web-video/web-video-plugin')
}
if (this.currentLoadOptions.mode === 'p2p-media-loader' && this.moduleLoaded.p2pMediaLoader !== true) {
const [ p2pMediaLoaderModule ] = await Promise.all([
import('@peertube/p2p-media-loader-hlsjs'),
import('./shared/p2p-media-loader/hls-plugin'),
import('./shared/p2p-media-loader/p2p-media-loader-plugin')
])
this.p2pMediaLoaderModule = p2pMediaLoaderModule
}
}
private async tryToRecoverHLSError (err: any) { private async tryToRecoverHLSError (err: any) {
if (err.code === MediaError.MEDIA_ERR_DECODE) { if (err.code === MediaError.MEDIA_ERR_DECODE) {

View File

@ -0,0 +1,38 @@
import videojs from 'video.js'
import { ContextMenuItemOptions } from '../../types'
const MenuItem = videojs.getComponent('MenuItem')
class ContextMenuItem extends MenuItem {
options_: ContextMenuItemOptions
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor (player: videojs.Player, options: ContextMenuItemOptions) {
super(player, options)
}
handleClick (e: videojs.EventTarget.Event) {
super.handleClick(e)
this.options_.listener(e)
// Close the containing menu after the call stack clears.
setTimeout(() => {
this.player().contextMenu().menu.dispose()
}, 1)
}
createEl (type: string, props?: any, attrs?: any) {
const el = super.createEl(type, props, attrs)
const newEl = videojs.dom.createEl('span')
newEl.innerHTML = `<span class="vjs-menu-item-text">${this.localize(this.options_.label)}</span>`
el.replaceChild(newEl, el.querySelector('.vjs-menu-item-text'))
return el
}
}
export { ContextMenuItem }

View File

@ -0,0 +1,119 @@
import videojs, { VideoJsPlayer } from 'video.js'
import { ContextMenuPluginOptions } from '../../types'
import { ContextMenu } from './context-menu'
import { getPointerPosition } from './util'
const Plugin = videojs.getPlugin('plugin')
class ContextMenuPlugin extends Plugin {
options_: ContextMenuPluginOptions & videojs.MenuOptions
menu: ContextMenu
private onContextMenuBind: (e: TouchEvent & MouseEvent) => void
constructor (player: videojs.Player, options: ContextMenuPluginOptions & videojs.MenuOptions) {
super(player, options)
this.options_ = options
// If we have already invoked the plugin, teardown before setting up again.
if (this.menu) {
this.menu.dispose()
this.player.off('contextmenu', this.onContextMenuBind)
}
this.onContextMenuBind = this.onContextMenu.bind(this)
this.player.on('contextmenu', this.onContextMenuBind)
this.player.ready(() => this.player.addClass('vjs-contextmenu-ui'))
}
private onContextMenu (e: TouchEvent & MouseEvent) {
// If this event happens while the custom menu is open, close it and do
// nothing else. This will cause native contextmenu events to be intercepted
// once again; so, the next time a contextmenu event is encountered, we'll
// open the custom menu.
if (hasMenu(this.player)) {
this.menu.dispose()
return
}
if (excludeElements(e.target as HTMLElement)) return
// Calculate the positioning of the menu based on the player size and
// triggering event.
const pointerPosition = getPointerPosition(this.player.el() as HTMLElement, e)
const playerSize = this.player.el().getBoundingClientRect()
const menuPosition = findMenuPosition(pointerPosition, playerSize)
// A workaround for Firefox issue where "oncontextmenu" event
// leaks "click" event to document https://bugzilla.mozilla.org/show_bug.cgi?id=990614
const documentEl = (videojs.browser as any).IS_FIREFOX ? document.documentElement : document
e.preventDefault()
const menu = this.menu = new ContextMenu(this.player, {
content: this.options_.content,
position: menuPosition
})
menu.on('dispose', () => {
for (const event of [ 'click', 'tap' ]) {
// eslint-disable-next-line @typescript-eslint/unbound-method
videojs.off(documentEl as Element, event, menu.dispose)
}
this.player.removeChild(menu)
this.menu = undefined
})
this.player.addChild(menu)
const menuEl = menu.el() as HTMLElement
const menuSize = menuEl.getBoundingClientRect()
const bodySize = document.body.getBoundingClientRect()
if (menuSize.right > bodySize.width || menuSize.bottom > bodySize.height) {
menuEl.style.left = Math.floor(Math.min(
menuPosition.left,
this.player.currentWidth() - menu.currentWidth()
)) + 'px'
menuEl.style.top = Math.floor(Math.min(
menuPosition.top,
this.player.currentHeight() - menu.currentHeight()
)) + 'px'
}
for (const event of [ 'click', 'tap' ]) {
// eslint-disable-next-line @typescript-eslint/unbound-method
videojs.on(documentEl as Element, event, menu.dispose)
}
}
}
videojs.registerPlugin('contextMenu', ContextMenuPlugin)
export { ContextMenuPlugin }
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
function hasMenu (player: VideoJsPlayer) {
if (!player.usingPlugin('contextMenu')) return false
return !!player.contextMenu().menu?.el()
}
function excludeElements (targetEl: HTMLElement) {
const tagName = targetEl.tagName.toLowerCase()
return tagName === 'input' || tagName === 'textarea'
}
function findMenuPosition (pointerPosition: { x?: number, y?: number }, playerSize: { height: number, width: number }) {
return {
left: Math.round(playerSize.width * pointerPosition.x),
top: Math.round(playerSize.height - (playerSize.height * pointerPosition.y))
}
}

View File

@ -0,0 +1,39 @@
import videojs from 'video.js'
import { ContextMenuItem } from './context-menu-item'
import { ContextMenuPluginOptions } from '../../types'
const Menu = videojs.getComponent('Menu')
type ContextMenuOptions = ContextMenuPluginOptions & { position: { left: number, top: number } }
class ContextMenu extends Menu {
options_: ContextMenuOptions & videojs.MenuOptions
constructor (player: videojs.Player, options: ContextMenuOptions) {
super(player, { ...options, menuButton: undefined })
// Each menu component has its own `dispose` method that can be
// safely bound and unbound to events while maintaining its context.
// eslint-disable-next-line @typescript-eslint/unbound-method
this.dispose = videojs.bind(this, this.dispose)
for (const c of options.content()) {
this.addItem(new ContextMenuItem(player, {
label: c.label,
listener: videojs.bind(player, c.listener)
}))
}
}
createEl () {
const el = super.createEl()
videojs.dom.addClass(el, 'vjs-contextmenu-ui-menu')
el.style.left = this.options_.position.left + 'px'
el.style.top = this.options_.position.top + 'px'
return el
}
}
export { ContextMenu }

View File

@ -0,0 +1,2 @@
// Thanks & credits: https://github.com/videojs/videojs-contextmenu-ui/
export * from './context-menu-plugin.js'

View File

@ -0,0 +1,52 @@
export function findElPosition (el: HTMLElement) {
let box: DOMRect
if (el.getBoundingClientRect && el.parentNode) {
box = el.getBoundingClientRect()
}
if (!box) return { left: 0, top: 0 }
const docEl = document.documentElement
const body = document.body
const clientLeft = docEl.clientLeft || body.clientLeft || 0
const scrollLeft = window.pageXOffset || body.scrollLeft
const left = box.left + scrollLeft - clientLeft
const clientTop = docEl.clientTop || body.clientTop || 0
const scrollTop = window.pageYOffset || body.scrollTop
const top = box.top + scrollTop - clientTop
// Android sometimes returns slightly off decimal values, so need to round
return {
left: Math.round(left),
top: Math.round(top)
}
}
export function getPointerPosition (el: HTMLElement, event: TouchEvent & MouseEvent) {
const position: { y?: number, x?: number } = {}
const box = findElPosition(el)
const boxW = el.offsetWidth
const boxH = el.offsetHeight
const boxY = box.top
const boxX = box.left
let pageY = event.pageY
let pageX = event.pageX
if (event.changedTouches) {
pageX = event.changedTouches[0].pageX
pageY = event.changedTouches[0].pageY
}
position.y = Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH))
position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW))
return position
}
export function isFunction (functionToCheck: any) {
return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]'
}

View File

@ -1,4 +1,3 @@
import { basename, dirname } from 'path'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'
class RedundancyUrlManager { class RedundancyUrlManager {
@ -10,7 +9,7 @@ class RedundancyUrlManager {
removeBySegmentUrl (segmentUrl: string) { removeBySegmentUrl (segmentUrl: string) {
logger.info(`Removing redundancy of segment URL ${segmentUrl}.`) logger.info(`Removing redundancy of segment URL ${segmentUrl}.`)
const baseUrl = dirname(segmentUrl) const baseUrl = getBaseUrl(segmentUrl)
this.baseUrls = this.baseUrls.filter(u => u !== baseUrl && u !== baseUrl + '/') this.baseUrls = this.baseUrls.filter(u => u !== baseUrl && u !== baseUrl + '/')
} }
@ -24,7 +23,7 @@ class RedundancyUrlManager {
const newBaseUrl = this.baseUrls[i] const newBaseUrl = this.baseUrls[i]
const slashPart = newBaseUrl.endsWith('/') ? '' : '/' const slashPart = newBaseUrl.endsWith('/') ? '' : '/'
return newBaseUrl + slashPart + basename(url) return newBaseUrl + slashPart + getFilename(url)
} }
countBaseUrls () { countBaseUrls () {
@ -41,3 +40,16 @@ class RedundancyUrlManager {
export { export {
RedundancyUrlManager RedundancyUrlManager
} }
// ---------------------------------------------------------------------------
function getFilename (url: string) {
return url.split('/').pop()
}
function getBaseUrl (url: string) {
const baseUrl = url.split('/')
baseUrl.pop()
return baseUrl.join('/')
}

View File

@ -1,4 +1,3 @@
import { basename } from 'path'
import { Segment } from '@peertube/p2p-media-loader-core' import { Segment } from '@peertube/p2p-media-loader-core'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'
import { wait } from '@root-helpers/utils' import { wait } from '@root-helpers/utils'
@ -32,7 +31,7 @@ export class SegmentValidator {
this.loadSha256SegmentsPromiseIfNeeded() this.loadSha256SegmentsPromiseIfNeeded()
const filename = basename(removeQueryParams(segment.url)) const filename = removeQueryParams(segment.url).split('/').pop()
const segmentValue = (await this.segmentJSONPromise)[filename] const segmentValue = (await this.segmentJSONPromise)[filename]

View File

@ -1,5 +1,5 @@
import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core' import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs' import { Engine, HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
import { LiveVideoLatencyMode } from '@peertube/peertube-models' import { LiveVideoLatencyMode } from '@peertube/peertube-models'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@ -17,10 +17,7 @@ type ConstructorOptions =
export class HLSOptionsBuilder { export class HLSOptionsBuilder {
constructor ( constructor (private options: ConstructorOptions) {
private options: ConstructorOptions,
private p2pMediaLoaderModule?: any
) {
} }
@ -50,7 +47,7 @@ export class HLSOptionsBuilder {
'filter:internal.player.p2p-media-loader.options.result', 'filter:internal.player.p2p-media-loader.options.result',
this.getP2PMediaLoaderOptions({ redundancyUrlManager, segmentValidator }) this.getP2PMediaLoaderOptions({ redundancyUrlManager, segmentValidator })
) )
const loader = new this.p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass() as P2PMediaLoader const loader = new Engine(p2pMediaLoaderConfig).createLoaderClass() as unknown as P2PMediaLoader
const p2pMediaLoader: P2PMediaLoaderPluginOptions = { const p2pMediaLoader: P2PMediaLoaderPluginOptions = {
requiresUserAuth: this.options.requiresUserAuth, requiresUserAuth: this.options.requiresUserAuth,
@ -212,7 +209,8 @@ export class HLSOptionsBuilder {
backBufferLength: 90, backBufferLength: 90,
startLevel: -1, startLevel: -1,
testBandwidth: false, testBandwidth: false,
debug: false debug: false,
enableWorker: false
} }
} }

View File

@ -20,6 +20,7 @@ import { WebVideoPlugin } from '../shared/web-video/web-video-plugin'
import { PlayerMode } from './peertube-player-options' import { PlayerMode } from './peertube-player-options'
import { SegmentValidator } from '../shared/p2p-media-loader/segment-validator' import { SegmentValidator } from '../shared/p2p-media-loader/segment-validator'
import { ChaptersPlugin } from '../shared/control-bar/chapters-plugin' import { ChaptersPlugin } from '../shared/control-bar/chapters-plugin'
import { ContextMenuPlugin } from '../shared/context-menu'
declare module 'video.js' { declare module 'video.js' {
@ -51,7 +52,7 @@ declare module 'video.js' {
peertubeResolutions (): PeerTubeResolutionsPlugin peertubeResolutions (): PeerTubeResolutionsPlugin
contextmenuUI (options?: any): any contextMenu (options?: ContextMenuPluginOptions): ContextMenuPlugin
bezels (): BezelsPlugin bezels (): BezelsPlugin
peertubeMobile (): PeerTubeMobilePlugin peertubeMobile (): PeerTubeMobilePlugin
@ -141,6 +142,19 @@ type MetricsPluginOptions = {
videoUUID: () => string videoUUID: () => string
} }
type ContextMenuPluginOptions = {
content: () => {
icon?: string
label: string
listener: () => void
}[]
}
type ContextMenuItemOptions = {
listener: (e: videojs.EventTarget.Event) => void
label: string
}
type StoryboardOptions = { type StoryboardOptions = {
url: string url: string
width: number width: number
@ -294,8 +308,10 @@ export {
PeerTubePluginOptions, PeerTubePluginOptions,
WebVideoPluginOptions, WebVideoPluginOptions,
P2PMediaLoaderPluginOptions, P2PMediaLoaderPluginOptions,
ContextMenuItemOptions,
PeerTubeResolution, PeerTubeResolution,
VideoJSPluginOptions, VideoJSPluginOptions,
ContextMenuPluginOptions,
UpNextPluginOptions, UpNextPluginOptions,
LoadedQualityData, LoadedQualityData,
StoryboardOptions, StoryboardOptions,

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,32 @@
import { ApplicationRef, enableProdMode, APP_INITIALIZER, importProvidersFrom } from '@angular/core'
import { enableDebugTools, BrowserModule, bootstrapApplication } from '@angular/platform-browser'
import { environment } from './environments/environment'
import { logger } from './root-helpers'
import { AppComponent } from './app/app.component'
import routes from './app/app.routes'
import { ServiceWorkerModule } from '@angular/service-worker'
import { polyfillICU } from './app/helpers'
import { tap } from 'rxjs/operators'
import {
ServerService,
PluginService,
RedirectService, PreloadSelectedModulesList,
MenuGuards,
CustomReuseStrategy,
getCoreProviders
} from './app/core'
import { APP_BASE_HREF, registerLocaleData } from '@angular/common' import { APP_BASE_HREF, registerLocaleData } from '@angular/common'
import localeOc from '@app/helpers/locales/oc'
import { RouteReuseStrategy, provideRouter, withInMemoryScrolling, withPreloading } from '@angular/router'
import { provideHttpClient } from '@angular/common/http' import { provideHttpClient } from '@angular/common/http'
import { APP_INITIALIZER, ApplicationRef, enableProdMode, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'
import { BrowserModule, bootstrapApplication, enableDebugTools } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { RouteReuseStrategy, provideRouter, withInMemoryScrolling, withPreloading } from '@angular/router'
import { ServiceWorkerModule } from '@angular/service-worker'
import localeOc from '@app/helpers/locales/oc'
import { getFormProviders } from '@app/shared/shared-forms/shared-form-providers'
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
import { LoadingBarModule } from '@ngx-loading-bar/core' import { LoadingBarModule } from '@ngx-loading-bar/core'
import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { ToastModule } from 'primeng/toast' import { ToastModule } from 'primeng/toast'
import { tap } from 'rxjs/operators'
import { AppComponent } from './app/app.component'
import routes from './app/app.routes'
import {
CustomReuseStrategy,
MenuGuards,
PluginService,
PreloadSelectedModulesList,
RedirectService,
ServerService,
getCoreProviders
} from './app/core'
import { polyfillICU } from './app/helpers'
import { getMainProviders } from './app/shared/shared-main/main-providers' import { getMainProviders } from './app/shared/shared-main/main-providers'
import { getFormProviders } from '@app/shared/shared-forms/shared-form-providers' import { environment } from './environments/environment'
import { logger } from './root-helpers'
registerLocaleData(localeOc, 'oc') registerLocaleData(localeOc, 'oc')
@ -51,6 +52,8 @@ logger.registerServerSending(environment.apiUrl)
const bootstrap = () => bootstrapApplication(AppComponent, { const bootstrap = () => bootstrapApplication(AppComponent, {
providers: [ providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
importProvidersFrom( importProvidersFrom(
BrowserModule, BrowserModule,
BrowserAnimationsModule, BrowserAnimationsModule,

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-implied-eval */ /* eslint-disable @typescript-eslint/no-implied-eval */
import * as debug from 'debug' import debug from 'debug'
import { firstValueFrom, ReplaySubject } from 'rxjs' import { firstValueFrom, ReplaySubject } from 'rxjs'
import { first, shareReplay } from 'rxjs/operators' import { first, shareReplay } from 'rxjs/operators'
import { RegisterClientHelpers } from 'src/types/register-client-option.model' import { RegisterClientHelpers } from 'src/types/register-client-option.model'

View File

@ -3,7 +3,7 @@
@use '_icons' as *; @use '_icons' as *;
/* stylelint-disable */ /* stylelint-disable */
@import '~primeng/resources/primeng.css'; @import 'primeng/resources/primeng.css';
// Override primeng style we don't want // Override primeng style we don't want
input[type=button] { input[type=button] {

View File

@ -1 +0,0 @@
module.exports = require('path-browserify')

View File

@ -1,3 +1,3 @@
tsconfig.json tsconfig.json
*.ts *.ts
webpack.config.ts vite.config.mjs

View File

@ -4,7 +4,7 @@
"version": "0.0.7", "version": "0.0.7",
"description": "API to communicate with the PeerTube player embed", "description": "API to communicate with the PeerTube player embed",
"scripts": { "scripts": {
"build": "../../../node_modules/.bin/tsc && ../../../node_modules/.bin/webpack --mode production --config ./webpack.config.js" "build": "rm -rf ./build ./dist && ../../../node_modules/.bin/tsc && ../../../node_modules/.bin/vite build --mode production --config ./vite.config.mjs"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -0,0 +1,30 @@
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
import { defineConfig } from 'vite'
import checker from 'vite-plugin-checker'
const __dirname = dirname(fileURLToPath(import.meta.url));
export default defineConfig(() => {
return {
build: {
outDir: resolve(__dirname, 'build'),
emptyOutDir: true,
minify: 'esbuild',
lib: {
name: 'PeerTubePlayer',
fileName: () => `player.min.js`,
formats: [ 'umd' ],
entry: './player.ts'
}
},
plugins: [
checker({
typescript: {
tsconfigPath: resolve(__dirname, 'tsconfig.json')
}
})
]
}
})

View File

@ -84,5 +84,7 @@
} }
</script> </script>
<script type="module" src="./embed.ts"></script>
</body> </body>
</html> </html>

View File

@ -67,5 +67,7 @@
<!-- iframes are used dynamically --> <!-- iframes are used dynamically -->
<iframe hidden></iframe> <iframe hidden></iframe>
<a hidden></a> <a hidden></a>
<script type="module" src="./test-embed.ts"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,55 @@
import { defineConfig } from 'vite'
import { resolve } from 'path'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
import { dirname } from 'path'
import { fileURLToPath } from 'url'
import checker from 'vite-plugin-checker'
const __dirname = dirname(fileURLToPath(import.meta.url))
const root = resolve(__dirname, '../../../')
export default defineConfig(() => {
return {
base: '/client/standalone/videos/',
root: resolve(root, 'src', 'standalone', 'videos'),
resolve: {
alias: [
{ find: /^video.js$/, replacement: resolve(root, './node_modules/video.js/core.js') },
{ find: /^hls.js$/, replacement: resolve(root, './node_modules/hls.js/dist/hls.light.mjs') },
{ find: '@root-helpers', replacement: resolve(root, './src/root-helpers') }
],
},
css: {
preprocessorOptions: {
scss: {
includePaths: [resolve(root, './src/sass/include')]
}
}
},
build: {
outDir: resolve(root, 'dist', 'standalone', 'videos'),
emptyOutDir: true,
rollupOptions: {
input: {
embed: resolve(root, 'src', 'standalone', 'videos', 'embed.html'),
'test-embed': resolve(root, 'src', 'standalone', 'videos', 'test-embed.html')
},
},
},
plugins: [
checker({
typescript: {
tsconfigPath: resolve(root, 'src', 'standalone', 'videos', 'tsconfig.json')
}
}),
nodePolyfills()
]
}
})

View File

@ -1,19 +1,18 @@
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"downlevelIteration": true,
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"moduleResolution": "node", "moduleResolution": "node",
"module": "es2020", "module": "es2020",
"esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"noImplicitAny": true, "noImplicitAny": true,
"noImplicitThis": true, "noImplicitThis": true,
"alwaysStrict": true, "alwaysStrict": true,
"allowJs": true, "allowJs": true,
"importHelpers": true, "importHelpers": true,
"allowSyntheticDefaultImports": true,
"strictBindCallApply": true, "strictBindCallApply": true,
"target": "ES2022", "target": "ES2022",
"typeRoots": [ "typeRoots": [
@ -44,9 +43,6 @@
"fs": [ "fs": [
"src/shims/noop.ts" "src/shims/noop.ts"
], ],
"path": [
"src/shims/path.ts"
],
"crypto": [ "crypto": [
"src/shims/noop.ts" "src/shims/noop.ts"
] ]

View File

@ -1,27 +0,0 @@
const path = require('path')
// Helper functions
const ROOT = path.resolve(__dirname, '..')
const EVENT = process.env.npm_lifecycle_event || ''
function hasProcessFlag (flag) {
return process.argv.join('').indexOf(flag) > -1
}
function hasNpmFlag (flag) {
return EVENT.includes(flag)
}
function isWebpackDevServer () {
return process.argv[1] && !!(/webpack-dev-server$/.exec(process.argv[1]))
}
function root (args) {
args = Array.prototype.slice.call(arguments, 0)
return path.join.apply(path, [ROOT].concat(args))
}
exports.hasProcessFlag = hasProcessFlag
exports.hasNpmFlag = hasNpmFlag
exports.isWebpackDevServer = isWebpackDevServer
exports.root = root

View File

@ -1,218 +0,0 @@
const helpers = require('./helpers')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const ProvidePlugin = require('webpack/lib/ProvidePlugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = function () {
const configuration = {
entry: {
'video-embed': './src/standalone/videos/embed.ts',
'player': './src/standalone/embed-player-api/player.ts',
'test-embed': './src/standalone/videos/test-embed.ts'
},
resolve: {
/*
* An array of extensions that should be used to resolve modules.
*
* See: http://webpack.github.io/docs/configuration.html#resolve-extensions
*/
extensions: [ '.ts', '.js', '.json', '.scss' ],
modules: [ helpers.root('src'), 'node_modules' ],
symlinks: true,
alias: {
'video.js$': path.resolve('node_modules/video.js/core.js'),
'hls.js$': path.resolve('node_modules/hls.js/dist/hls.light.js'),
'@root-helpers': path.resolve('src/root-helpers')
},
fallback: {
fs: [ path.resolve('src/shims/noop.ts') ],
path: [ path.resolve('src/shims/path.ts') ],
crypto: [ path.resolve('src/shims/noop.ts') ]
}
},
output: {
path: helpers.root('dist/standalone/videos'),
filename: process.env.ANALYZE_BUNDLE === 'true'
? '[name].bundle.js'
: '[name].[contenthash].bundle.js',
sourceMapFilename: '[file].map',
chunkFilename: process.env.ANALYZE_BUNDLE === 'true'
? '[name].chunk.js'
: '[id].[contenthash].chunk.js',
publicPath: '/client/standalone/videos/'
},
devtool: process.env.NODE_ENV === 'production' ? false : 'source-map',
module: {
rules: [
{
test: /\.ts$/,
use: [
getBabelLoader(),
{
loader: 'ts-loader',
options: {
configFile: helpers.root('src/standalone/videos/tsconfig.json')
}
}
]
},
{
test: /\.m?js$/,
use: [ getBabelLoader() ]
},
{
test: /\.(sass|scss)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 1
}
},
{
loader: 'sass-loader',
options: {
sassOptions: {
sourceMap: true,
includePaths: [
helpers.root('src/sass/include')
]
}
}
}
]
},
{
test: /\.html$/,
exclude: [
helpers.root('src/index.html'),
helpers.root('src/standalone/videos/embed.html'),
helpers.root('src/standalone/videos/test-embed.html')
],
type: 'asset/source'
},
{
test: /\.(jpg|png|gif|svg)$/,
type: 'asset'
},
{
test: /\.(ttf|eot|woff2?)$/,
type: 'asset'
}
]
},
plugins: [
new ProvidePlugin({
process: 'process/browser',
Buffer: [ 'buffer', 'Buffer' ]
}),
new MiniCssExtractPlugin({
filename: process.env.ANALYZE_BUNDLE === 'true'
? '[name].css'
: '[name].[contenthash].css'
}),
new HtmlWebpackPlugin({
template: 'src/standalone/videos/embed.html',
filename: 'embed.html',
title: 'PeerTube',
chunksSortMode: 'auto',
inject: 'body',
chunks: [ 'video-embed' ],
minify: {
collapseWhitespace: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
}
}),
new HtmlWebpackPlugin({
template: '!!html-loader!src/standalone/videos/test-embed.html',
filename: 'test-embed.html',
title: 'PeerTube',
chunksSortMode: 'auto',
inject: 'body',
chunks: [ 'test-embed' ]
})
],
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
ecma: 6,
warnings: false,
ie8: false,
safari10: false,
mangle: true,
compress: {
passes: 3,
pure_getters: true
},
output: {
ascii_only: true,
comments: false
}
}
})
]
},
performance: {
maxEntrypointSize: 700000, // 600kB
maxAssetSize: 700000
},
node: {
global: true
}
}
return configuration
}
function getBabelLoader () {
return {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env', {
targets: 'last 1 Chrome version, last 2 Edge major versions, Firefox ESR, Safari >= 12, ios_saf >= 12'
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -62,7 +62,7 @@ if [ -z ${1+x} ] || ([ "$1" != "--light" ] && [ "$1" != "--analyze-bundle" ]); t
for key in "${!languages[@]}"; do for key in "${!languages[@]}"; do
lang=${languages[$key]} lang=${languages[$key]}
mv "dist/build/$key" "dist/$lang" mv "dist/build/browser/$key" "dist/$lang"
if [ "$lang" != "en-US" ]; then if [ "$lang" != "en-US" ]; then
# Do not duplicate assets # Do not duplicate assets
@ -72,13 +72,14 @@ if [ -z ${1+x} ] || ([ "$1" != "--light" ] && [ "$1" != "--analyze-bundle" ]); t
mv "./dist/$defaultLanguage/assets" "./dist" mv "./dist/$defaultLanguage/assets" "./dist"
rmdir "dist/build" rm -r "dist/build"
cp "./dist/$defaultLanguage/manifest.webmanifest" "./dist/manifest.webmanifest"
else else
additionalParams="" additionalParams=""
if [ ! -z ${1+x} ] && [ "$1" == "--analyze-bundle" ]; then if [ ! -z ${1+x} ] && [ "$1" == "--analyze-bundle" ]; then
additionalParams="--named-chunks=true --output-hashing=none" additionalParams="--named-chunks=true --output-hashing=none"
# For webpack # For Vite
export ANALYZE_BUNDLE=true export ANALYZE_BUNDLE=true
fi fi
@ -86,8 +87,6 @@ else
--configuration production --stats-json $additionalParams --configuration production --stats-json $additionalParams
fi fi
cp "./dist/$defaultLanguage/manifest.webmanifest" "./dist/manifest.webmanifest"
cd ../ && npm run build:embed && cd client/ cd ../ && npm run build:embed && cd client/
# Copy runtime locales # Copy runtime locales

View File

@ -2,12 +2,4 @@
set -eu set -eu
cd client cd client && ./node_modules/.bin/vite -c ./src/standalone/videos/vite.config.mjs build --mode=production
mkdir -p ./dist/standalone/videos/
if [ ! -z ${ANALYZE_BUNDLE+x} ] && [ "$ANALYZE_BUNDLE" == true ]; then
NODE_ENV=production npm run webpack -- --config webpack/webpack.video-embed.js --mode production --json > "./dist/standalone/videos/embed-stats.json"
else
NODE_ENV=production npm run webpack -- --config webpack/webpack.video-embed.js --mode production
fi

View File

@ -2,8 +2,6 @@
set -eu set -eu
gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/standalone/videos/embed-stats.json
npm run concurrently -- -k \ npm run concurrently -- -k \
"cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en-US/stats.json" \ "cd client/src/standalone/videos/ && npx vite-bundle-visualizer" \
"cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/standalone/videos/embed-stats.json" "cd client && npx esbuild-visualizer --metadata ./dist/en-US/stats.json"

View File

@ -8,7 +8,7 @@ if [ ! -z ${2+x} ] && [ "$2" = "--ar-locale" ]; then
clientConfiguration="ar-locale" clientConfiguration="ar-locale"
fi fi
clientCommand="cd client && node --max_old_space_size=4096 node_modules/.bin/ng serve --proxy-config proxy.config.json --hmr --configuration $clientConfiguration --host 0.0.0.0 --disable-host-check --port 3000" clientCommand="cd client && node --max_old_space_size=4096 node_modules/.bin/ng serve --proxy-config proxy.config.json --hmr --configuration $clientConfiguration --host 0.0.0.0 --port 3000"
serverCommand="NODE_ENV=dev node dist/server" serverCommand="NODE_ENV=dev node dist/server"
if [ ! -z ${1+x} ] && [ "$1" = "--skip-server" ]; then if [ ! -z ${1+x} ] && [ "$1" = "--skip-server" ]; then

View File

@ -5,5 +5,5 @@ set -eu
npm run build:server npm run build:server
npm run concurrently -- -k \ npm run concurrently -- -k \
"cd client && npm run webpack -- --config webpack/webpack.video-embed.js --mode development --watch" \ "cd client && ./node_modules/.bin/vite -c ./src/standalone/videos/vite.config.mjs build -w --mode=development" \
"NODE_ENV=dev npm start" "NODE_ENV=dev npm start"

View File

@ -4,8 +4,7 @@ set -eu
cd client/src/standalone/embed-player-api cd client/src/standalone/embed-player-api
rm -rf dist build && tsc -p . && ../../../node_modules/.bin/webpack --config ./webpack.config.js npm run build
npm publish --access public npm publish --access public
rm -rf dist build node_modules rm -rf dist build node_modules

View File

@ -35,7 +35,7 @@ export class PageHtml {
static async getEmbedHTML () { static async getEmbedHTML () {
const path = this.getEmbedHTMLPath() const path = this.getEmbedHTMLPath()
// Disable HTML cache in dev mode because webpack can regenerate JS files // Disable HTML cache in dev mode because Vite can regenerate JS files
if (!isTestOrDevInstance() && this.htmlCache[path]) { if (!isTestOrDevInstance() && this.htmlCache[path]) {
return this.htmlCache[path] return this.htmlCache[path]
} }