Skip to content

Commit

Permalink
feat(hls,dash): sort quality
Browse files Browse the repository at this point in the history
  • Loading branch information
shiyiya authored Sep 11, 2024
1 parent 0d87e59 commit 326325d
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 109 deletions.
49 changes: 1 addition & 48 deletions packages/dash/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ npm i @oplayer/core @oplayer/dash dashjs

```html
<script src="https://cdn.jsdelivr.net/npm/@oplayer/core@latest/dist/index.min.js"></script>
<!-- dash FIRST -->
<script src="http://cdn.dashjs.org/latest/dash.all.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@oplayer/dash@latest/dist/index.min.js"></script>

<div id="oplayer" />
Expand All @@ -28,52 +26,7 @@ npm i @oplayer/core @oplayer/dash dashjs
poster: 'https://cdn.jsdelivr.net/gh/shiyiya/QI-ABSL@master/o/poster.png'
}
})
.use([ODash()])
.use([ODash({ library: 'https://cdn.dashjs.org/latest/dash.all.min.js' })])
.create()
</script>
```

## Usage

```ts
export type Matcher = (video: HTMLVideoElement, source: Source) => boolean

// active inactive
export type Active = (
instance: MediaPlayerClass,
library: typeof import('dashjs')
) => void | ((instance: MediaPlayerClass, library: typeof import('dashjs')) => void)

export interface DashPluginOptions {
matcher?: Matcher
/**
* config for dashjs
*
* @type {MediaPlayerSettingClass}
*/
config?: MediaPlayerSettingClass
/**
* enable quality control for the 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
}
```
2 changes: 1 addition & 1 deletion packages/dash/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oplayer/dash",
"version": "1.2.25",
"version": "1.2.26-beta.0",
"description": "Dash plugin for oplayer",
"type": "module",
"main": "./dist/index.es.js",
Expand Down
138 changes: 97 additions & 41 deletions packages/dash/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { loadSDK, type Player, type PlayerPlugin, type RequiredPartial, type Source } from '@oplayer/core'
import type { MediaPlayerClass, MediaPlayerSettingClass, QualityChangeRenderedEvent } from 'dashjs'
import type {
BitrateInfo,
MediaPlayerClass,
MediaPlayerSettingClass,
QualityChangeRenderedEvent
} from 'dashjs'

const PLUGIN_NAME = 'oplayer-plugin-dash'

Expand All @@ -8,6 +13,22 @@ export type Matcher = (video: HTMLVideoElement, source: Source) => boolean
export interface DashPluginOptions {
library?: string
matcher?: Matcher

/**
* default auto
*/
defaultQuality?: (levels: BitrateInfo[]) => number

/**
* default browser language
*/
defaultAudio?: (tracks: dashjs.MediaInfo[]) => number

/**
* default browser language
*/
defaultSubtitle?: (tracks: dashjs.MediaInfo[]) => number

/**
* config for dashjs
*
Expand Down Expand Up @@ -68,7 +89,10 @@ class DashPlugin implements PlayerPlugin {
qualityControl: true,
withBitrate: false,
qualitySwitch: 'immediate',
matcher: defaultMatcher
matcher: defaultMatcher,
defaultQuality: () => -1,
defaultAudio: () => -1,
defaultSubtitle: () => -1
}

constructor(options?: DashPluginOptions) {
Expand Down Expand Up @@ -127,46 +151,49 @@ function getSettingsByType(instance: MediaPlayerClass, type: 'video', withBitrat
const isAuto = Boolean(instance.getSettings().streaming?.abr?.autoSwitchBitrate?.video)
const videoQuality = instance.getQualityFor('video')
if (bitrateInfoList.length > 1) {
return bitrateInfoList.map((it) => {
let name = it.height + 'p'

if (withBitrate) {
const kb = it.bitrate / 1000
const useMb = kb > 1000
const number = useMb ? (kb / 1000).toFixed(2) : Math.floor(kb)
name += ` (${number}${useMb ? 'm' : 'k'}bps)`
}
return bitrateInfoList
.toSorted((a, b) => b.bitrate - a.bitrate)
.map((it) => {
let name = it.height + 'p'

if (withBitrate) {
const kb = it.bitrate / 1000
const useMb = kb > 1000
const number = useMb ? (kb / 1000).toFixed(2) : Math.floor(kb)
name += ` (${number}${useMb ? 'm' : 'k'}bps)`
}

return {
name,
default: isAuto ? false : videoQuality == it.qualityIndex,
value: it.qualityIndex
}
})
return {
name,
default: isAuto ? false : videoQuality == it.qualityIndex,
value: it.qualityIndex
}
})
}

return []
}

const generateSetting = (player: Player, instance: MediaPlayerClass, options: DashPluginOptions) => {
const generateSetting = (player: Player, instance: MediaPlayerClass, options: DashPlugin['options']) => {
instance.on(DashPlugin.library.MediaPlayer.events.STREAM_INITIALIZED, function () {
if (options.qualityControl) {
const quality = instance.getBitrateInfoListFor('video')
if (quality.length < 2) return

const defaultLevel = options.defaultQuality(quality)
if (defaultLevel != -1) instance.setQualityFor('video', defaultLevel)

settingUpdater({
name: 'Quality',
icon: player.context.ui.icons.quality,
settings() {
const ex = getSettingsByType(instance, 'video', options.withBitrate)

if (ex.length) {
ex.unshift({
settings: () =>
[
{
name: player.locales.get('Auto'),
default: Boolean(instance.getSettings().streaming?.abr?.autoSwitchBitrate?.video),
value: -1
})
}

return ex
},
}
].concat(getSettingsByType(instance, 'video', options.withBitrate)),
onChange({ value }) {
instance.updateSettings({
streaming: { abr: { autoSwitchBitrate: { video: value == -1 } } }
Expand All @@ -191,11 +218,27 @@ const generateSetting = (player: Player, instance: MediaPlayerClass, options: Da
}

if (options.audioControl) {
const audioTracks = instance.getTracksFor('audio')
if (audioTracks.length < 2) return

let defaultAudio: number | undefined = options.defaultAudio(audioTracks)
if (defaultAudio == -1) {
defaultAudio = audioTracks.find(({ lang }) => {
return lang === navigator.language || lang === navigator.language.split('-')[0]
})?.id as unknown as number
}

if (defaultAudio != -1 && defaultAudio != undefined) {
instance.setCurrentTrack(audioTracks.find((t) => (t.id as unknown as number) == defaultAudio)!)
}

console.log(audioTracks!)

settingUpdater({
name: 'Language',
icon: player.context.ui.icons.lang,
settings() {
return instance.getTracksFor('audio').map((it) => ({
return audioTracks.map((it) => ({
name: it.lang || 'unknown',
default: instance.getCurrentTrackFor('audio')?.id == it.id,
value: it
Expand All @@ -208,23 +251,38 @@ const generateSetting = (player: Player, instance: MediaPlayerClass, options: Da
}

if (options.textControl) {
const textTracks = instance.getTracksFor('text')
if (textTracks.length < 1) return

let defaultSubtitle: number | undefined = options.defaultSubtitle(textTracks)
if (defaultSubtitle == -1) {
defaultSubtitle = textTracks.find(({ lang }) => {
return lang === navigator.language || lang === navigator.language.split('-')[0]
})?.id as unknown as number
}

if (defaultSubtitle != -1 && defaultSubtitle != undefined) {
instance.enableText(true)
instance.setTextTrack(defaultSubtitle)
}

settingUpdater({
name: 'Subtitle',
icon: player.context.ui.icons.subtitle,
settings() {
const ex = instance.getTracksFor('text').map((it) => ({
name: it.lang || 'unknown',
default: instance.getCurrentTrackFor('text')?.id == it.id,
value: it.id
}))
if (ex.length) {
ex.unshift({
return [
{
name: player.locales.get('Off'),
default: !instance.isTextEnabled(),
value: -1 as any
})
}
return ex
}
].concat(
textTracks.map((it) => ({
name: it.lang || 'unknown',
default: instance.getCurrentTrackFor('text')?.id == it.id,
value: it.id
}))
)
},
onChange({ value }) {
instance.enableText(value != -1)
Expand All @@ -245,8 +303,6 @@ const generateSetting = (player: Player, instance: MediaPlayerClass, options: Da
onChange: (it: { value: any }) => void
}) {
const settings = arg.settings()
if (settings.length < 2) return

const { name, icon, onChange } = arg

player.context.ui.setting.unregister(`${PLUGIN_NAME}-${name}`)
Expand Down
3 changes: 2 additions & 1 deletion packages/docs/src/pages/docs/guide/start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ Player.make('#oplayer', {

<Tab>
```html filename="app.html"
<script src="https://cdn.jsdelivr.net/npm/@oplayer/core@latest/dist/index.ui.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@oplayer/core@latest/dist/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@oplayer/ui@latest/dist/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@oplayer/hls@latest/dist/index.min.js"></script>

<script>
Expand Down
6 changes: 5 additions & 1 deletion packages/docs/src/pages/docs/ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ npm i @oplayer/ui
```

```html
<!-- Recommend -->
<script src="https://cdn.jsdelivr.net/npm/@oplayer/core@latest/dist/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@oplayer/ui@latest/dist/index.core.js"></script>

<!-- or just one -->
<!-- or just one **@oplayer/ui version updated with core** -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@oplayer/core@latest/dist/index.ui.js"></script> -->

<!-- or **@oplayer/core version updated with ui** -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@oplayer/ui@latest/dist/index.ui.js"></script> -->
```

## Basic
Expand Down
2 changes: 1 addition & 1 deletion packages/hls/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oplayer/hls",
"version": "1.2.27-beta.2",
"version": "1.2.27-beta.3",
"description": "Hls plugin for oplayer",
"type": "module",
"main": "./dist/index.es.js",
Expand Down
34 changes: 18 additions & 16 deletions packages/hls/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,22 +211,24 @@ const generateSetting = (player: Player, instance: Hls, options: HlsPlugin['opti
icon: ui.icons.quality,
name: 'Quality',
settings() {
return instance.levels.reduce(
(pre, level, id) => {
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 == id, value: id })
return pre
},
[{ name: player.locales.get('Auto'), default: instance.autoLevelEnabled, value: -1 }]
)
return instance.levels
.toSorted((a, b) => b.bitrate - a.bitrate)
.reduce(
(pre, level, id) => {
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 == id, value: id })
return pre
},
[{ name: player.locales.get('Auto'), default: instance.autoLevelEnabled, value: -1 }]
)
},
onChange(it) {
if (options.qualitySwitch == 'immediate') {
Expand Down

0 comments on commit 326325d

Please sign in to comment.