]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/assets/player/video-renderer.ts
bda40b11d0ceaac7dda380c1bc860679e1915273
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / video-renderer.ts
1 // Thanks: https://github.com/feross/render-media
2 // TODO: use render-media once https://github.com/feross/render-media/issues/32 is fixed
3
4 import { extname } from 'path'
5 import * as MediaElementWrapper from 'mediasource'
6 import * as videostream from 'videostream'
7
8 const VIDEOSTREAM_EXTS = [
9 '.m4a',
10 '.m4v',
11 '.mp4'
12 ]
13
14 type RenderMediaOptions = {
15 controls: boolean
16 autoplay: boolean
17 }
18
19 function renderVideo (
20 file,
21 elem: HTMLVideoElement,
22 opts: RenderMediaOptions,
23 callback: (err: Error, renderer: any) => void
24 ) {
25 validateFile(file)
26
27 return renderMedia(file, elem, opts, callback)
28 }
29
30 function renderMedia (file, elem: HTMLVideoElement, opts: RenderMediaOptions, callback: (err: Error, renderer: any) => void) {
31 const extension = extname(file.name).toLowerCase()
32 let preparedElem = undefined
33 let currentTime = 0
34 let renderer
35
36 if (VIDEOSTREAM_EXTS.indexOf(extension) >= 0) {
37 renderer = useVideostream()
38 } else {
39 renderer = useMediaSource()
40 }
41
42 function useVideostream () {
43 prepareElem()
44 preparedElem.addEventListener('error', fallbackToMediaSource)
45 preparedElem.addEventListener('loadstart', onLoadStart)
46 return videostream(file, preparedElem)
47 }
48
49 function useMediaSource () {
50 prepareElem()
51 preparedElem.addEventListener('error', callback)
52 preparedElem.addEventListener('loadstart', onLoadStart)
53
54 const wrapper = new MediaElementWrapper(preparedElem)
55 const writable = wrapper.createWriteStream(getCodec(file.name))
56 file.createReadStream().pipe(writable)
57
58 if (currentTime) preparedElem.currentTime = currentTime
59
60 return wrapper
61 }
62
63 function fallbackToMediaSource () {
64 preparedElem.removeEventListener('error', fallbackToMediaSource)
65
66 useMediaSource()
67 }
68
69 function prepareElem () {
70 if (preparedElem === undefined) {
71 preparedElem = elem
72
73 preparedElem.addEventListener('progress', function () {
74 currentTime = elem.currentTime
75 })
76 }
77 }
78
79 function onLoadStart () {
80 preparedElem.removeEventListener('loadstart', onLoadStart)
81 if (opts.autoplay) preparedElem.play()
82
83 callback(null, renderer)
84 }
85 }
86
87 function validateFile (file) {
88 if (file == null) {
89 throw new Error('file cannot be null or undefined')
90 }
91 if (typeof file.name !== 'string') {
92 throw new Error('missing or invalid file.name property')
93 }
94 if (typeof file.createReadStream !== 'function') {
95 throw new Error('missing or invalid file.createReadStream property')
96 }
97 }
98
99 function getCodec (name: string) {
100 const ext = extname(name).toLowerCase()
101 return {
102 '.m4a': 'audio/mp4; codecs="mp4a.40.5"',
103 '.m4v': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
104 '.mkv': 'video/webm; codecs="avc1.640029, mp4a.40.5"',
105 '.mp3': 'audio/mpeg',
106 '.mp4': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
107 '.webm': 'video/webm; codecs="opus, vorbis, vp8"'
108 }[ext]
109 }
110
111 export {
112 renderVideo
113 }