Skip to content

Commit b3ff837

Browse files
Migrate video player from video.js to shaka-player (#4978)
* Migrate video player from video.js to shaka-player * Fix syntax error introduced during cleanup * Fix typo Co-authored-by: ChunkyProgrammer <[email protected]> * Show bitrate in stats for Invidious legacy formats * Sort legacy formats by bitrate instead of qualityLabel * Use Invidious' newly added isPostLiveDvr property * Invidious correctly returns the ratelimit for DASH manifests now * Properly hide unsupported screenshot functionality outside of Electron * Sort captions in data initialiser * Update shaka-player to version 4.8.1 * Use textTrackLabelFormat configuration option * Switch to manifestPreprocessorTXml * Add support for VR videos that use equirectangular projection * The AV1 video streams work okay for vr, so allow list them too * Use woff2 (169 KB) Material Icons font instead of otf (390 KB) * Use material icons for custom player elements * Fix legacy formats exception * Update shaka-player to version 4.8.3 * Sort default quality values the same as in the player * Cleanup representations search in live subtitle fix * Slightly speed up sortCaptions * Fix setPositionState error when playback rate is 0 * Update shaka-player to version 4.8.4 * Fix playback rate changes getting overridden * Fix current chapter index not updating * Fix next video not automatically playing * Fix volume changes not propagating to the mute button * Reduce work done during UI config updates * Fix UI customizations not applying after UI config changes * Update shaka-player to version 4.8.5 * Replace non-reactive workaround with a Vue 3 friendly one * Fix error when all sponsorblock segments are set to do nothing * Add some guards to hopefully reduce errors during fast naviagtions * Make SponsorBlock failure non-fatal * Update shaka-player to version 4.8.6 * Update shaka-player to version 4.8.8 * Update shaka-player to version 4.9.0 * Update shaka-player to version 4.9.1 * Minor performance improvements * Update shaka-player to version 4.9.6 * Update shaka-player to version 4.9.9 * Fix sponsor block markers not showing up * Add some basic error messages for expired urls and ratelimits * Add some basic error messages for 403s * Cleanup code comments * Migrate player to composition API for better performance * Use reactive for stats instead of ref, as it never gets reassigned * Update shaka-player to version 4.10.0 * Fix i18n import in custom player components * Add chapter markers * Move shaka-player CSS import into the Vue file * Bump mpd_version from 5 to 7 * Apply suggestions from code review Co-authored-by: ChunkyProgrammer <[email protected]> * Fix seeking with the arrow keys * Update shaka-player to version 4.10.3 * Truncate long video titles to the screen width in the full screen overlay * Fix mouse scroll handlers firing multiple times * Cleanup unneeded code * Update shaka-player to version 4.10.6 * Various small optimisations * Move skipped sponsorblock segement messages to the top right corner * Support prefers-reduced-transparency in overlays * Fix stylelint errors * Update shaka-player to version 4.10.7 * Update shaka-player to version 4.10.8 * Only use variants that are predicted to play smoothly * Only set preferredDecodingAttributes for DASH playback * Implement a custom audio track selector instead of using shaka-player's * Move skipped sponsorblock segement messages to the bottom right corner * Fix scrolling over the big play pause button not working * Update shaka-player to version 4.10.9 * Fix some type issues * Add support for secondary audio tracks * Downgrade shaka-player to fix subtitle alignment * Actually downgrade shaka-player * Fix Invidious API error * Update outdated comment * Fix multiple audio track detection for Invidious * Fix duplicate qualities with the Invidious API * Use vp9 streams if the Invidious instance is running a new enough version * When an error occurs with the thumbnails just log it * Include the video ID in the error logs * Gracefully handle the internet connection disappearing during playback * Show a message while buffering if it was caused by the internet connection disappearing * Fix the text color and RTL handling * Cleanup the Invidious DASH manifest in builds without the local API * Fix quality selection when switching from audio to DASH Unfortunately shaka-player will still override the quality selections with its ABR bug. * Update shaka-player to version 4.10.10 * Fix position and alignment for auto-generated subtitles * Update shaka-player to version 4.10.11 * Use the HLS manifests for live streams The live DASH manifests are currently unusable on both API backends as they return 403s after 1 minute of playback. Unfortunately this means we lose the ability to seek and use the audio formats for live streams. * Update shaka-player to version 4.10.12 --------- Co-authored-by: ChunkyProgrammer <[email protected]>
1 parent fa901d3 commit b3ff837

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+5436
-7161
lines changed

.stylelintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"selector-pseudo-class-no-unknown": [
2727
true,
2828
{
29-
"ignorePseudoClasses": ["deep"]
29+
"ignorePseudoClasses": ["deep", "global"]
3030
}
3131
],
3232
"a11y/no-outline-none": true,

_scripts/_domParser.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

_scripts/dev-runner.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ const web = process.argv.indexOf('--web') !== -1
1919
let mainConfig
2020
let rendererConfig
2121
let webConfig
22+
let SHAKA_LOCALES_TO_BE_BUNDLED
2223

2324
if (!web) {
2425
mainConfig = require('./webpack.main.config')
2526
rendererConfig = require('./webpack.renderer.config')
27+
28+
SHAKA_LOCALES_TO_BE_BUNDLED = rendererConfig.SHAKA_LOCALES_TO_BE_BUNDLED
29+
delete rendererConfig.SHAKA_LOCALES_TO_BE_BUNDLED
2630
} else {
2731
webConfig = require('./webpack.web.config')
2832
}
@@ -128,17 +132,27 @@ function startRenderer(callback) {
128132
})
129133

130134
const server = new WebpackDevServer({
131-
static: {
132-
directory: path.resolve(__dirname, '..', 'static'),
133-
watch: {
134-
ignored: [
135-
/(dashFiles|storyboards)\/*/,
136-
'/**/.DS_Store',
137-
'**/static/locales/*'
138-
]
135+
static: [
136+
{
137+
directory: path.resolve(__dirname, '..', 'static'),
138+
watch: {
139+
ignored: [
140+
/(dashFiles|storyboards)\/*/,
141+
'/**/.DS_Store',
142+
'**/static/locales/*'
143+
]
144+
},
145+
publicPath: '/static'
139146
},
140-
publicPath: '/static'
141-
},
147+
{
148+
directory: path.resolve(__dirname, '..', 'node_modules', 'shaka-player', 'ui', 'locales'),
149+
publicPath: '/static/shaka-player-locales',
150+
watch: {
151+
// Ignore everything that isn't one of the locales that we would bundle in production mode
152+
ignored: `**/!(${SHAKA_LOCALES_TO_BE_BUNDLED.join('|')}).json`
153+
}
154+
}
155+
],
142156
port
143157
}, compiler)
144158

_scripts/getShakaLocales.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
const { readFileSync, readdirSync } = require('fs')
2+
3+
function getPreloadedLocales() {
4+
const localesFile = readFileSync(`${__dirname}/../node_modules/shaka-player/dist/locales.js`, 'utf-8')
5+
6+
const localesLine = localesFile.match(/^\/\/ LOCALES: ([\w, -]+)$/m)
7+
8+
if (!localesLine) {
9+
throw new Error("Failed to parse shaka-player's preloaded locales")
10+
}
11+
12+
return localesLine[1].split(',').map(locale => locale.trim())
13+
}
14+
15+
function getAllLocales() {
16+
const filenames = readdirSync(`${__dirname}/../node_modules/shaka-player/ui/locales`)
17+
18+
return new Set(filenames
19+
.filter(filename => filename !== 'source.json' && filename.endsWith('.json'))
20+
.map(filename => filename.replace('.json', '')))
21+
}
22+
23+
/**
24+
* Maps the shaka locales to FreeTube's active ones
25+
* This allows us to know which locale files are actually needed
26+
* and which shaka locale needs to be activated for a given FreeTube one.
27+
* @param {Set<string>} shakaLocales
28+
* @param {string[]} freeTubeLocales
29+
*/
30+
function getMappings(shakaLocales, freeTubeLocales) {
31+
/**
32+
* @type {[string, string][]}
33+
* Using this structure as it gets passed to `new Map()` in the player component
34+
* The first element is the FreeTube locale, the second one is the shaka-player one
35+
**/
36+
const mappings = []
37+
38+
for (const locale of freeTubeLocales) {
39+
if (shakaLocales.has(locale)) {
40+
mappings.push([
41+
locale,
42+
locale
43+
])
44+
} else if (shakaLocales.has(locale.replace('_', '-'))) {
45+
mappings.push([
46+
locale,
47+
locale.replace('_', '-')
48+
])
49+
} else if (shakaLocales.has(locale.split(/[-_]/)[0])) {
50+
mappings.push([
51+
locale,
52+
locale.split(/[-_]/)[0]
53+
])
54+
}
55+
}
56+
57+
// special cases
58+
59+
mappings.push(
60+
// according to https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
61+
// "no" is the macro language for "nb" and "nn"
62+
[
63+
'nb_NO',
64+
'no'
65+
],
66+
[
67+
'nn',
68+
'no'
69+
],
70+
71+
// according to https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
72+
// "iw" is the old/original code for Hebrew, these days it's "he"
73+
[
74+
'he',
75+
'iw'
76+
],
77+
78+
// not sure why we have pt, pt-PT and pt-BR in the FreeTube locales
79+
// as pt and pt-PT are the same thing, but we should handle it here anyway
80+
[
81+
'pt',
82+
'pt-PT'
83+
]
84+
)
85+
86+
return mappings
87+
}
88+
89+
function getShakaLocales() {
90+
const shakaLocales = getAllLocales()
91+
92+
/** @type {string[]} */
93+
const freeTubeLocales = JSON.parse(readFileSync(`${__dirname}/../static/locales/activeLocales.json`, 'utf-8'))
94+
95+
const mappings = getMappings(shakaLocales, freeTubeLocales)
96+
97+
const preloaded = getPreloadedLocales()
98+
99+
const shakaMappings = mappings.map(mapping => mapping[1])
100+
101+
// use a set to deduplicate the list
102+
// we don't need to bundle any locale files that are already embedded in shaka-player/preloaded
103+
104+
/** @type {string[]} */
105+
const toBeBundled = [...new Set(shakaMappings.filter(locale => !preloaded.includes(locale)))]
106+
107+
return {
108+
SHAKA_LOCALE_MAPPINGS: mappings,
109+
SHAKA_LOCALES_PREBUNDLED: preloaded,
110+
SHAKA_LOCALES_TO_BE_BUNDLED: toBeBundled
111+
}
112+
}
113+
114+
module.exports = getShakaLocales()

_scripts/patchShaka.mjs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// This script fixes shaka not exporting its type definitions and referencing remote google fonts in its CSS
2+
// by adding an export line to the type definitions and downloading the fonts and updating the CSS to point to the local files
3+
// this script only makes changes if they are needed, so running it multiple times doesn't cause any problems
4+
5+
import { appendFileSync, closeSync, ftruncateSync, openSync, readFileSync, writeFileSync, writeSync } from 'fs'
6+
import { resolve } from 'path'
7+
8+
const SHAKA_DIST_DIR = resolve(import.meta.dirname, '../node_modules/shaka-player/dist')
9+
10+
function fixTypes() {
11+
let fixedTypes = false
12+
13+
let fileHandleNormal
14+
try {
15+
fileHandleNormal = openSync(`${SHAKA_DIST_DIR}/shaka-player.ui.d.ts`, 'a+')
16+
17+
const contents = readFileSync(fileHandleNormal, 'utf-8')
18+
19+
// This script is run after every `yarn install`, even if shaka-player wasn't updated
20+
// So we want to check first, if we actually need to make any changes
21+
// or if the ones from the previous run are still intact
22+
if (!contents.includes('export default shaka')) {
23+
appendFileSync(fileHandleNormal, 'export default shaka;\n')
24+
25+
fixedTypes = true
26+
}
27+
} finally {
28+
if (typeof fileHandleNormal !== 'undefined') {
29+
closeSync(fileHandleNormal)
30+
}
31+
}
32+
33+
let fileHandleDebug
34+
try {
35+
fileHandleDebug = openSync(`${SHAKA_DIST_DIR}/shaka-player.ui.debug.d.ts`, 'a+')
36+
37+
const contents = readFileSync(fileHandleDebug, 'utf-8')
38+
39+
// This script is run after every `yarn install`, even if shaka-player wasn't updated
40+
// So we want to check first, if we actually need to make any changes
41+
// or if the ones from the previous run are still intact
42+
if (!contents.includes('export default shaka')) {
43+
appendFileSync(fileHandleDebug, 'export default shaka;\n')
44+
45+
fixedTypes = true
46+
}
47+
} finally {
48+
if (typeof fileHandleDebug !== 'undefined') {
49+
closeSync(fileHandleDebug)
50+
}
51+
}
52+
53+
if (fixedTypes) {
54+
console.log('Fixed shaka-player types')
55+
}
56+
}
57+
58+
async function removeRobotoFont() {
59+
let cssFileHandle
60+
try {
61+
cssFileHandle = openSync(`${SHAKA_DIST_DIR}/controls.css`, 'r+')
62+
63+
let cssContents = readFileSync(cssFileHandle, 'utf-8')
64+
65+
const beforeReplacement = cssContents.length
66+
cssContents = cssContents.replace(/@font-face\{font-family:Roboto;[^}]+\}/, '')
67+
68+
if (cssContents.length !== beforeReplacement) {
69+
ftruncateSync(cssFileHandle)
70+
writeSync(cssFileHandle, cssContents, 0, 'utf-8')
71+
72+
console.log('Removed shaka-player Roboto font, so it uses ours')
73+
}
74+
} finally {
75+
if (typeof cssFileHandle !== 'undefined') {
76+
closeSync(cssFileHandle)
77+
}
78+
}
79+
}
80+
81+
async function replaceAndDownloadMaterialIconsFont() {
82+
let cssFileHandle
83+
try {
84+
cssFileHandle = openSync(`${SHAKA_DIST_DIR}/controls.css`, 'r+')
85+
86+
let cssContents = readFileSync(cssFileHandle, 'utf-8')
87+
88+
const fontFaceRegex = /@font-face{font-family:'Material Icons Round'[^}]+format\('opentype'\)}/
89+
90+
if (fontFaceRegex.test(cssContents)) {
91+
const cssResponse = await fetch('https://fonts.googleapis.com/icon?family=Material+Icons+Round', {
92+
headers: {
93+
// Without the user-agent it returns the otf file instead of the woff2 one
94+
'user-agent': 'Firefox/125.0'
95+
}
96+
})
97+
98+
const text = await cssResponse.text()
99+
100+
let newFontCSS = text.match(/(@font-face\s*{[^}]+})/)[1].replaceAll('\n', '')
101+
102+
103+
const urlMatch = newFontCSS.match(/https:\/\/fonts\.gstatic\.com\/s\/materialiconsround\/(?<version>[^\/]+)\/[^.]+\.(?<extension>[\w]+)/)
104+
105+
const url = urlMatch[0]
106+
const { version, extension } = urlMatch.groups
107+
108+
const fontResponse = await fetch(url)
109+
const fontContent = new Uint8Array(await fontResponse.arrayBuffer())
110+
111+
const filename = `shaka-materialiconsround-${version}.${extension}`
112+
writeFileSync(`${SHAKA_DIST_DIR}/${filename}`, fontContent)
113+
114+
newFontCSS = newFontCSS.replace(url, `./${filename}`)
115+
116+
cssContents = cssContents.replace(fontFaceRegex, newFontCSS)
117+
118+
ftruncateSync(cssFileHandle)
119+
writeSync(cssFileHandle, cssContents, 0, 'utf-8')
120+
121+
console.log('Changed shaka-player Material Icons Rounded font to use the smaller woff2 format instead of otf')
122+
console.log('Downloaded shaka-player Material Icons Rounded font')
123+
}
124+
} catch (e) {
125+
console.error(e)
126+
} finally {
127+
if (typeof cssFileHandle !== 'undefined') {
128+
closeSync(cssFileHandle)
129+
}
130+
}
131+
}
132+
133+
fixTypes()
134+
await removeRobotoFont()
135+
await replaceAndDownloadMaterialIconsFont()

_scripts/webpack.renderer.config.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
77
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
88
const ProcessLocalesPlugin = require('./ProcessLocalesPlugin')
99
const CopyWebpackPlugin = require('copy-webpack-plugin')
10+
const {
11+
SHAKA_LOCALE_MAPPINGS,
12+
SHAKA_LOCALES_PREBUNDLED,
13+
SHAKA_LOCALES_TO_BE_BUNDLED
14+
} = require('./getShakaLocales')
1015

1116
const isDevMode = process.env.NODE_ENV === 'development'
1217

@@ -122,7 +127,9 @@ const config = {
122127
'process.env.SUPPORTS_LOCAL_API': true,
123128
'process.env.LOCALE_NAMES': JSON.stringify(processLocalesPlugin.localeNames),
124129
'process.env.GEOLOCATION_NAMES': JSON.stringify(readdirSync(path.join(__dirname, '..', 'static', 'geolocations')).map(filename => filename.replace('.json', ''))),
125-
'process.env.SWIPER_VERSION': `'${swiperVersion}'`
130+
'process.env.SWIPER_VERSION': `'${swiperVersion}'`,
131+
'process.env.SHAKA_LOCALE_MAPPINGS': JSON.stringify(SHAKA_LOCALE_MAPPINGS),
132+
'process.env.SHAKA_LOCALES_PREBUNDLED': JSON.stringify(SHAKA_LOCALES_PREBUNDLED)
126133
}),
127134
new HtmlWebpackPlugin({
128135
excludeChunks: ['processTaskWorker'],
@@ -143,7 +150,21 @@ const config = {
143150
transformAll: (assets) => {
144151
return Buffer.concat(assets.map(asset => asset.data))
145152
}
146-
}
153+
},
154+
// Don't need to copy them in dev mode,
155+
// as we configure WebpackDevServer to serve them
156+
...(isDevMode ? [] : [
157+
{
158+
from: path.join(__dirname, '../node_modules/shaka-player/ui/locales', `{${SHAKA_LOCALES_TO_BE_BUNDLED.join(',')}}.json`).replaceAll('\\', '/'),
159+
to: path.join(__dirname, '../dist/static/shaka-player-locales'),
160+
context: path.join(__dirname, '../node_modules/shaka-player/ui/locales'),
161+
transform: {
162+
transformer: (input) => {
163+
return JSON.stringify(JSON.parse(input.toString('utf-8')))
164+
}
165+
}
166+
}
167+
])
147168
]
148169
})
149170
],
@@ -156,14 +177,18 @@ const config = {
156177

157178
'youtubei.js$': 'youtubei.js/web',
158179

159-
// video.js's mpd-parser uses @xmldom/xmldom so that it can support both node and web browsers
160-
// as FreeTube only runs in electron and web browsers we can use the native DOMParser class, instead of the "polyfill"
161-
// https://caniuse.com/mdn-api_domparser
162-
'@xmldom/xmldom$': path.resolve(__dirname, '_domParser.js')
180+
// change to "shaka-player.ui.debug.js" to get debug logs (update jsconfig to get updated types)
181+
'shaka-player$': 'shaka-player/dist/shaka-player.ui.js',
163182
},
164183
extensions: ['.js', '.vue']
165184
},
166185
target: 'electron-renderer',
167186
}
168187

188+
if (isDevMode) {
189+
// hack to pass it through to the dev-runner.js script
190+
// gets removed there before the config object is passed to webpack
191+
config.SHAKA_LOCALES_TO_BE_BUNDLED = SHAKA_LOCALES_TO_BE_BUNDLED
192+
}
193+
169194
module.exports = config

0 commit comments

Comments
 (0)