Skip to content

Commit

Permalink
feat: use auto quality (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
kitayoshi authored Dec 23, 2020
1 parent 79a8cff commit 6798882
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 58 deletions.
36 changes: 7 additions & 29 deletions example/hls/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,27 @@ import React from 'react'
import {hot} from 'react-hot-loader'
import PlayerContainer from 'griffith'

const duration = 89

const sources = {
hd: {
bitrate: 905,
size: 10105235,
duration,
// 注意,这里手动提供了 auto 品质的 source,因此会无视 useAutoQuality 的配置
auto: {
format: 'm3u8',
width: 640,
height: 480,
play_url:
'http://zhihu-video-output.oss-cn-hangzhou.aliyuncs.com/test/hd-m3u8/999f95fc-0346-11e9-b494-0a580a44d740.m3u8',
play_url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
},
sd: {
bitrate: 580,
size: 6531802,
duration,
format: 'm3u8',
width: 320,
height: 240,
play_url:
'http://zhihu-video-output.oss-cn-hangzhou.aliyuncs.com/test/sd-m3u8/999f95fc-0346-11e9-b494-0a580a44d740.m3u8',
},
ld: {
bitrate: 261,
size: 2984172,
duration,
format: 'm3u8',
width: 160,
height: 120,
play_url:
'http://zhihu-video-output.oss-cn-hangzhou.aliyuncs.com/test/ld-m3u8/999f95fc-0346-11e9-b494-0a580a44d740.m3u8',
'https://test-streams.mux.dev/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8',
},
}

const props = {
id: 'zhihu2018',
title: '2018 我们如何与世界相处?',
id: 'test-hls-video',
title: 'Test HLS Video',
standalone: true,
cover: 'https://zhstatic.zhihu.com/cfe/griffith/player.png',
duration,
sources,
shouldObserveResize: true,
autoplay: true,
}

const App = () => <PlayerContainer {...props} />
Expand Down
63 changes: 53 additions & 10 deletions packages/griffith-hls/src/Video.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,62 @@ import Hls from 'hls.js/dist/hls.light.min'
import {getMasterM3U8Blob} from './utils'

export default class Video extends Component {
manuallyBuildAdaptiveM3U8Blob = false
hasLoadStarted = false

componentDidMount() {
const {src, sources, useAutoQuality} = this.props
this.hls = new Hls({autoStartLoad: false})
this.hls.attachMedia(this.video)
const {sources} = this.props
const master = getMasterM3U8Blob(sources)
this.src = URL.createObjectURL(master)

const isAutoQualitySourceProvided = Boolean(
sources.find(s => s.quality === 'auto')
)

// 启用自动质量但是又没有提供 auto 规格的 source,那么就尝试本地手动生成
if (useAutoQuality && !isAutoQualitySourceProvided) {
const master = getMasterM3U8Blob(sources)
this.src = URL.createObjectURL(master)
this.manuallyBuildAdaptiveM3U8Blob = true
} else {
this.src = src
}

this.hls.loadSource(this.src)
}

componentDidUpdate(prevProps) {
const {currentQuality, sources, paused} = this.props

// 切换清晰度
if (currentQuality !== prevProps.currentQuality) {
const source = sources.find(source => source.quality === currentQuality)
const source = sources.find(s => s.quality === currentQuality)
if (source) {
const levels = this.hls.levels
const level = levels.findIndex(item => item.url.includes(source.source))
this.hls.nextLevel = level
if (this.manuallyBuildAdaptiveM3U8Blob) {
const levels = this.hls.levels
const level = levels.findIndex(l => l.url.includes(source.source))
this.hls.nextLevel = level
} else {
// TODO: 没有在 hls 的 API 内部找到顺畅切换 source 的方法
// 因此这里比较直接和生硬
const currentTime = this.video.currentTime
this.hls.destroy()
this.hls = new Hls({autoStartLoad: false})
this.hls.attachMedia(this.video)
this.hls.loadSource(source.source)
this.video.currentTime = currentTime
this.hls.startLoad()
if (!paused) {
this.video.play()
}
}
} else {
// 一定意味着选择了手动生成的「auto」
this.hls.nextLevel = -1
}
}

// 切换播放状态
if (!paused && prevProps.paused && !this.hasLoadStarted) {
this.hls.startLoad()
this.hasLoadStarted = true
Expand All @@ -35,12 +67,23 @@ export default class Video extends Component {

componentWillUnmount() {
this.hls.destroy()
URL.revokeObjectURL(this.src)
if (this.manuallyBuildAdaptiveM3U8Blob) {
URL.revokeObjectURL(this.src)
}
}

render() {
// eslint-disable-next-line no-unused-vars
const {onRef, currentQuality, src, sources, paused, ...props} = this.props
const {
onRef,
/* eslint-disable no-unused-vars */
currentQuality,
useAutoQuality,
src,
sources,
paused,
/* eslint-enable no-unused-vars */
...props
} = this.props
return (
<video
ref={el => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ http://zhihu-video-output.oss-cn-hangzhou.aliyuncs.com/test/sd-m3u8/999f95fc-034
http://zhihu-video-output.oss-cn-hangzhou.aliyuncs.com/test/hd-m3u8/999f95fc-0346-11e9-b494-0a580a44d740.m3u8"
`;

exports[`create master m3u8 palylist wihtout resolution 1`] = `
exports[`create master m3u8 palylist without resolution 1`] = `
"#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1029120
http://zhihu-video-output.oss-cn-hangzhou.aliyuncs.com/test/sd-m3u8/999f95fc-0346-11e9-b494-0a580a44d740.m3u8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('create master m3u8', () => {
expect(createMasterM3U8(list)).toMatchSnapshot()
})

it('palylist wihtout resolution', () => {
it('palylist without resolution', () => {
const list = [
{
bandwidth: 1029120,
Expand Down
5 changes: 3 additions & 2 deletions packages/griffith-mp4/src/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,20 @@ export default class Player extends Component {
}

render() {
/* eslint-disable no-unused-vars */
const {
/* eslint-disable no-unused-vars */
src,
onRef,
currentQuality,
useAutoQuality,
onSeeking,
onPlay,
paused,
onTimeUpdate,
onProgress,
/* eslint-enable no-unused-vars */
...props
} = this.props
/* eslint-enable no-unused-vars */
return (
<video
ref={el => {
Expand Down
1 change: 1 addition & 0 deletions packages/griffith/README-zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ render(<Player {...props} />)
| `duration` | `number` | | 初始视频时长。在视频元数据载入后使用实际值 |
| `sources` | `sources` | | 视频播放数据。具体见下, |
| `defaultQuality` | `ld \| sd \| hd \| fhd` | | 视频默认播放清晰度 |
| `useAutoQuality` | `boolean` | `false` | 是否启用自动清晰度功能 |
| `standalone` | `boolean` | `false` | 是否启用 standalone 模式 |
| `onBeforePlay` | `function` | `void` | 视频播放之前回调函数 |
| `shouldObserveResize` | `boolean` | `false` | 是否监听窗口 resize |
Expand Down
1 change: 1 addition & 0 deletions packages/griffith/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ render(<Player {...props} />)
| `duration` | `number` | | Initial video duration. Use actual values after video metadata is loaded |
| `sources` | `sources` | | Video playback data |
| `defaultQuality` | `ld \| sd \| hd \| fhd` | | Video default quality |
| `useAutoQuality` | `boolean` | `false` | Enable auto quality |
| `standalone` | `boolean` | `false` | Enable standalone mode |
| `onBeforePlay` | `function` | `void` | Callback function before video playback |
| `shouldObserveResize` | `boolean` | `false` | Listen to the window resize |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`CombinedTimeItem get CombinedTimeItem component 1`] = `
<div
className="time_uz62bc-o_O-fullScreenedTime_1xxtrer"
className="time_kviad4-o_O-fullScreenedTime_1xxtrer"
>
<div
className=""
Expand Down
2 changes: 2 additions & 0 deletions packages/griffith/src/components/Player/Player.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ class Player extends Component {
standalone,
onEvent,
useMSE,
useAutoQuality,
disablePictureInPicture,
} = this.props

Expand Down Expand Up @@ -444,6 +445,7 @@ class Player extends Component {
onProgress={this.handleVideoProgress}
onEvent={onEvent}
useMSE={useMSE}
useAutoQuality={useAutoQuality}
/>
</div>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const PlayerContainer = ({
autoplay,
disablePictureInPicture,
defaultQuality,
useAutoQuality = false,
}) => (
<ObjectFitProvider initialObjectFit={initialObjectFit}>
<PositionProvider shouldObserveResize={shouldObserveResize}>
Expand All @@ -39,12 +40,14 @@ const PlayerContainer = ({
sources={sources}
id={id}
defaultQuality={defaultQuality}
useAutoQuality={useAutoQuality}
>
<LocaleContext.Provider value={locale}>
<VideoSourceContext.Consumer>
{({currentSrc}) => (
<Player
useMSE={useMSE}
useAutoQuality={useAutoQuality}
autoplay={autoplay}
disablePictureInPicture={disablePictureInPicture}
standalone={standalone}
Expand Down Expand Up @@ -72,17 +75,17 @@ PlayerContainer.propTypes = {
standalone: PropTypes.bool,
id: PropTypes.string.isRequired,
title: PropTypes.string,
cover: PropTypes.string.isRequired,
duration: PropTypes.number.isRequired,
cover: PropTypes.string,
duration: PropTypes.number,
sources: PropTypes.objectOf(
PropTypes.shape({
bitrate: PropTypes.number.isRequired,
duration: PropTypes.number.isRequired,
bitrate: PropTypes.number,
duration: PropTypes.number,
format: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
height: PropTypes.number,
play_url: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
size: PropTypes.number,
width: PropTypes.number,
})
).isRequired,
error: PropTypes.shape({
Expand Down
17 changes: 13 additions & 4 deletions packages/griffith/src/components/Video/NormalVideo.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React from 'react'

// eslint-disable-next-line
const NormalVideo = ({onRef, paused, currentQuality, sources, ...props}) => (
<video {...props} ref={onRef} />
)
const NormalVideo = props => {
const {
onRef,
/* eslint-disable no-unused-vars */
paused,
currentQuality,
useAutoQuality,
sources,
/* eslint-disable no-unused-vars */
...restProps
} = props
return <video {...restProps} ref={onRef} />
}

export default {
pluginName: 'native',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class VideoSourceProvider extends React.Component {
onEvent: PropTypes.func.isRequired,
sources: PropTypes.object,
id: PropTypes.string.isRequired,
useAutoQuality: PropTypes.bool,
}

state = {
Expand All @@ -25,7 +26,7 @@ export default class VideoSourceProvider extends React.Component {
}

static getDerivedStateFromProps = (
{sources: videoSources, id, defaultQuality},
{sources: videoSources, id, defaultQuality, useAutoQuality},
state
) => {
if (!videoSources) return null
Expand All @@ -39,7 +40,13 @@ export default class VideoSourceProvider extends React.Component {

const sources = getSources(qualities, videoSources)

if (!isMobile && format === 'm3u8') {
// 目前只有直播流实现了手动拼接 auto 清晰度的功能
if (
useAutoQuality &&
!isMobile &&
format === 'm3u8' &&
!qualities.includes('auto')
) {
qualities.unshift('auto')
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const QUALITY_ORDER = ['ld', 'sd', 'hd', 'fhd']
const QUALITY_ORDER = ['auto', 'ld', 'sd', 'hd', 'fhd']

export const getQualities = (sources, isMobile) => {
const qualities = Object.keys(sources).sort(
Expand Down

0 comments on commit 6798882

Please sign in to comment.