From d0953b558feb05bef056f11e97f91caf486bf3dc Mon Sep 17 00:00:00 2001 From: CY Date: Mon, 26 Aug 2024 19:11:51 +0800 Subject: [PATCH] hls: use brower lang for audio and subtitle --- examples/standalone/main.ts | 10 +- packages/core/src/i18n/index.ts | 2 +- packages/danmaku/src/index.ts | 26 ++--- packages/hls/README.md | 50 ---------- packages/hls/package.json | 2 +- packages/hls/src/index.ts | 131 +++++++++++++++----------- packages/ui/package.json | 2 +- packages/ui/src/components/Setting.ts | 18 +--- 8 files changed, 105 insertions(+), 136 deletions(-) diff --git a/examples/standalone/main.ts b/examples/standalone/main.ts index ee647c7c..362cb819 100644 --- a/examples/standalone/main.ts +++ b/examples/standalone/main.ts @@ -133,7 +133,7 @@ const player = Player.make('#player', { } }), torrent(), - hls({ forceHLS: true, defaultQuality: 1080 }), + hls({ forceHLS: true }), dash(), mpegts(), danmaku({ @@ -144,6 +144,14 @@ const player = Player.make('#player', { new Playlist({ initialIndex: 0, sources: [ + { + title: 'hls - muti quality & subtitle & audio', + src: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8' + }, + { + title: 'dash - muti quality & subtitle & audio', + src: 'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd' + }, { title: 'HLS with SRT subtitle', src: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', diff --git a/packages/core/src/i18n/index.ts b/packages/core/src/i18n/index.ts index 3dedd9cb..e436d3cf 100644 --- a/packages/core/src/i18n/index.ts +++ b/packages/core/src/i18n/index.ts @@ -34,7 +34,7 @@ export default class I18n { return false }) } - if (!this.languages[this.lang]) this.lang = 'zh-CN' + if (!this.languages[this.lang]) this.lang = 'en' } get(key: string, ...arg: Array): string { diff --git a/packages/danmaku/src/index.ts b/packages/danmaku/src/index.ts index e3f11b9d..0124b0e7 100644 --- a/packages/danmaku/src/index.ts +++ b/packages/danmaku/src/index.ts @@ -29,7 +29,7 @@ export default (options = {} as Options): PlayerPlugin => ({ if (opacity) $danmaku.style.opacity = `${opacity}` if (options.enable == undefined) options.enable = true - let heatmapEnable = options.heatmap == undefined || options.heatmap || heatmap.length + let heatmapEnable = options.heatmap == undefined || options.heatmap let loaded = false const danmaku: DanmakuContext = new Danmaku({ @@ -37,7 +37,7 @@ export default (options = {} as Options): PlayerPlugin => ({ media: player.$video, engine: engine, comments: [], - speed: isMobile ? speed / 1.5 : speed + speed: isMobile ? speed / 1.5 : speed, }) as any danmaku.setFontSize = function setFontSize(value: number) { @@ -139,7 +139,7 @@ export default (options = {} as Options): PlayerPlugin => ({ } else { danmaku.hide() } - } + }, }, { name: player.locales.get('Heatmap'), @@ -149,7 +149,7 @@ export default (options = {} as Options): PlayerPlugin => ({ onChange: (value: Boolean) => { if (value) danmaku.heatmap?.enable() else danmaku.heatmap?.disable() - } + }, }, { type: 'selector', @@ -158,12 +158,12 @@ export default (options = {} as Options): PlayerPlugin => ({ children: [0.5, 0.75, 1, 1.25].map((it) => ({ name: `${it * 100}%`, value: it, - default: it == 1 + default: it == 1, })), onChange: ({ value }: any) => { options.fontSize = value danmaku.setFontSize(value) - } + }, }, { type: 'selector', @@ -172,11 +172,11 @@ export default (options = {} as Options): PlayerPlugin => ({ children: [0.3, 0.5, 0.8, 1].map((it) => ({ name: `${it * 100}%`, value: it, - default: it == (options?.opacity || 1) + default: it == (options?.opacity || 1), })), onChange: ({ value }: any) => { $danmaku.style.opacity = value - } + }, }, { type: 'selector', @@ -185,15 +185,15 @@ export default (options = {} as Options): PlayerPlugin => ({ children: [25, 50, 80, 100].map((it) => ({ name: `${it}%`, value: it, - default: it == area * 100 + default: it == area * 100, })), onChange: ({ value }: any) => { options.opacity = value $danmaku.style.height = `${value}%` danmaku.resize() - } - } - ] + }, + }, + ], }) } @@ -202,5 +202,5 @@ export default (options = {} as Options): PlayerPlugin => ({ if (displaySender && !isMobile) registerInput(player, danmaku, options) return danmaku - } + }, }) diff --git a/packages/hls/README.md b/packages/hls/README.md index 9a3e6bc8..14855dcf 100644 --- a/packages/hls/README.md +++ b/packages/hls/README.md @@ -38,56 +38,6 @@ npm i @oplayer/core @oplayer/hls hls.js ``` -## Usage - -```ts -export type Matcher = (video: HTMLVideoElement, source: Source, forceHLS: boolean) => boolean - -// active inactive -export type Active = ( - instance: Hls, - library: typeof import('hls.js/dist/hls.min.js') -) => void | ((instance: Hls, library: typeof import('hls.js/dist/hls.min.js')) => void) - -export interface HlsPluginOptions { - matcher?: Matcher - /** - * config for hls.js - * - * @type {Partial} - */ - config?: Partial - /** - * force use hls.js - * @type {boolean} false - */ - forceHLS?: boolean - /** - * enable quality control for the HLS stream, does not apply to the native (iPhone) clients. - * default: true - */ - qualityControl?: boolean - /** - * control how the stream quality is switched. default: immediate - * @value immediate: Trigger an immediate quality level switch to new quality level. This will abort the current fragment request if any, flush the whole buffer, and fetch fragment matching with current position and requested quality level. - * @value smooth: Trigger a quality level switch for next fragment. This could eventually flush already buffered next fragment. - */ - qualitySwitch?: 'immediate' | 'smooth' - /** - * @default: false - */ - withBitrate?: boolean - /** - * @default: true - */ - audioControl?: boolean - /** - * @default: true - */ - textControl?: boolean -} -``` - ## Handle Hls.js Error ```ts diff --git a/packages/hls/package.json b/packages/hls/package.json index a13a132b..94555c5a 100644 --- a/packages/hls/package.json +++ b/packages/hls/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/hls", - "version": "1.2.27-beta.0", + "version": "1.2.27-beta.1", "description": "Hls plugin for oplayer", "type": "module", "main": "./dist/index.es.js", diff --git a/packages/hls/src/index.ts b/packages/hls/src/index.ts index 6d93e054..c19c97d8 100644 --- a/packages/hls/src/index.ts +++ b/packages/hls/src/index.ts @@ -1,6 +1,6 @@ import { loadSDK, type Player, type PlayerPlugin, type RequiredPartial, type Source } from '@oplayer/core' import type Hls from 'hls.js' -import type { ErrorData, HlsConfig, LevelSwitchedData } from 'hls.js' +import type { ErrorData, HlsConfig, LevelSwitchedData, Level, MediaPlaylist } from 'hls.js' const PLUGIN_NAME = 'oplayer-plugin-hls' @@ -12,10 +12,19 @@ export interface HlsPluginOptions { matcher?: Matcher /** - * -1 : auto * default auto */ - defaultQuality?: number + defaultQuality?: (levels: Level[]) => number + + /** + * default browser language + */ + defaultAudio?: (tracks: MediaPlaylist[]) => number + + /** + * default browser language + */ + defaultSubtitle?: (tracks: MediaPlaylist[]) => number /** * custom handle hls fatal error @@ -91,7 +100,9 @@ class HlsPlugin implements PlayerPlugin { withBitrate: false, qualitySwitch: 'immediate', matcher: defaultMatcher, - defaultQuality: -1 + defaultQuality: () => -1, + defaultAudio: () => -1, + defaultSubtitle: () => -1 } constructor(options?: HlsPluginOptions) { @@ -192,35 +203,30 @@ const generateSetting = (player: Player, instance: Hls, options: HlsPlugin['opti const ui = player.context.ui if (options.qualityControl) { instance.once(HlsPlugin.library.Events.LEVEL_LOADED, () => { - let defaultSelect = options.defaultQuality + if (instance.levels.length < 1) return + const defaultLevel = options.defaultQuality(instance.levels) + if (defaultLevel != -1) instance.currentLevel = defaultLevel injectSetting({ icon: ui.icons.quality, name: 'Quality', settings() { - if (instance.levels.length > 1) { - return instance.levels.reduce( - (pre, level, i) => { - let name = (level.name || level.height).toString() - if (isFinite(+name)) name += 'p' - if (options.withBitrate) { - const kb = level.bitrate / 1000 - const useMb = kb > 1000 - const number = useMb ? (kb / 1000).toFixed(2) : Math.floor(kb) - name += ` (${number}${useMb ? 'm' : 'k'}bps)` - } - - if (level.height <= options.defaultQuality) { - defaultSelect = i - } - - pre.push({ name, default: instance.currentLevel == i, value: i }) - return pre - }, - [{ name: player.locales.get('Auto'), default: instance.autoLevelEnabled, value: -1 }] - ) - } - return [] + return instance.levels.reduce( + (pre, level) => { + let name = (level.name || level.height).toString() + if (isFinite(+name)) name += 'p' + if (options.withBitrate) { + const kb = level.bitrate / 1000 + const useMb = kb > 1000 + const number = useMb ? (kb / 1000).toFixed(2) : Math.floor(kb) + name += ` (${number}${useMb ? 'm' : 'k'}bps)` + } + + pre.push({ name, default: defaultLevel == level.id, value: level.id }) + return pre + }, + [{ name: player.locales.get('Auto'), default: instance.autoLevelEnabled, value: -1 }] + ) }, onChange(it) { if (options.qualitySwitch == 'immediate') { @@ -232,8 +238,6 @@ const generateSetting = (player: Player, instance: Hls, options: HlsPlugin['opti } } }) - - if (defaultSelect != -1) instance.currentLevel = defaultSelect }) instance.on( @@ -250,47 +254,64 @@ const generateSetting = (player: Player, instance: Hls, options: HlsPlugin['opti ) } - if (options.audioControl) - instance.on(HlsPlugin.library.Events.LEVEL_LOADED, () => { + if (options.audioControl) { + instance.once(HlsPlugin.library.Events.LEVEL_LOADED, () => { + if (instance.audioTracks.length < 1) return + + let defaultAudio: number | undefined = options.defaultAudio(instance.audioTracks) + if (defaultAudio == -1) { + defaultAudio = instance.audioTracks.find(({ lang }) => { + return lang === navigator.language || lang === navigator.language.split('-')[0] + })?.id + } + + if (defaultAudio != -1 && defaultAudio != undefined) instance.audioTrack = defaultAudio + injectSetting({ icon: ui.icons.lang, name: 'Language', settings() { - if (instance.audioTracks.length > 1) { - return instance.audioTracks.map(({ name, lang, id }) => ({ - name: lang || name, - default: instance.audioTrack == id, - value: id - })) - } - return [] + return instance.audioTracks.map(({ name, lang, id }) => ({ + name: lang || name, + default: defaultAudio == id, + value: id + })) }, onChange(it) { instance.audioTrack = it.value } }) }) + } if (options.textControl) - instance.on(HlsPlugin.library.Events.SUBTITLE_TRACK_LOADED, () => { + instance.once(HlsPlugin.library.Events.SUBTITLE_TRACK_LOADED, () => { + if (instance.subtitleTracks.length < 1) return + + let defaultSubtitle: number | undefined = options.defaultSubtitle(instance.subtitleTracks) + if (defaultSubtitle == -1) { + defaultSubtitle = instance.audioTracks.find(({ lang }) => { + return lang === navigator.language || lang === navigator.language.split('-')[0] + })?.id + } + + if (defaultSubtitle != -1 && defaultSubtitle != undefined) instance.subtitleTrack = defaultSubtitle + injectSetting({ icon: ui.icons.subtitle, name: 'Subtitle', settings() { - if (instance.subtitleTracks.length > 1) { - return instance.subtitleTracks.reduce( - (pre, { name, lang, id }) => { - pre.push({ - name: lang || name, - default: instance.subtitleTrack == id, - value: id - }) - return pre - }, - [{ name: player.locales.get('Off'), default: !instance.subtitleDisplay, value: -1 }] - ) - } - return [] + return instance.subtitleTracks.reduce( + (pre, { name, lang, id }) => { + pre.push({ + name: lang || name, + default: defaultSubtitle == id, + value: id + }) + return pre + }, + [{ name: player.locales.get('Off'), default: !instance.subtitleDisplay, value: -1 }] + ) }, onChange({ value }) { if ((instance.subtitleDisplay = !(value == -1))) { diff --git a/packages/ui/package.json b/packages/ui/package.json index 6ee72fea..84a14a0d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/ui", - "version": "1.3.3-beta.6", + "version": "1.3.3-beta.7", "description": "ui plugin for oplayer", "type": "module", "main": "./dist/index.es.js", diff --git a/packages/ui/src/components/Setting.ts b/packages/ui/src/components/Setting.ts index 51fd6a43..630bb0f1 100644 --- a/packages/ui/src/components/Setting.ts +++ b/packages/ui/src/components/Setting.ts @@ -215,19 +215,7 @@ function createPanel( } for (let i = 0; i < setting.length; i++) { - const { - name, - type, - key, - children, - icon, - default: selected, - onChange, - max, - min, - step, - value - } = setting[i]! + const { name, type, key, children, icon, default: selected, onChange, max, min, step } = setting[i]! const { $row, $label } = createRow( Object.assign( @@ -318,7 +306,9 @@ function createPanel( } // TODO: update methond } else { - $row.addEventListener('click', (e) => (onChange || options.parenOnChange)?.(value, e)) + if (type == 'option' || (type == undefined && !isSelectorOptionsPanel)) { + $row.addEventListener('click', (e) => (onChange || options.parenOnChange)?.(e)) + } } } }