aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player/video-renderer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/assets/player/video-renderer.ts')
-rw-r--r--client/src/assets/player/video-renderer.ts119
1 files changed, 119 insertions, 0 deletions
diff --git a/client/src/assets/player/video-renderer.ts b/client/src/assets/player/video-renderer.ts
new file mode 100644
index 000000000..8baa42533
--- /dev/null
+++ b/client/src/assets/player/video-renderer.ts
@@ -0,0 +1,119 @@
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
4import { extname } from 'path'
5import * as MediaElementWrapper from 'mediasource'
6import * as videostream from 'videostream'
7
8const VIDEOSTREAM_EXTS = [
9 '.m4a',
10 '.m4v',
11 '.mp4'
12]
13
14type RenderMediaOptions = {
15 controls: boolean
16 autoplay: boolean
17}
18
19function 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
30function 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 preparedElem.addEventListener('canplay', onCanPlay)
47 return videostream(file, preparedElem)
48 }
49
50 function useMediaSource () {
51 prepareElem()
52 preparedElem.addEventListener('error', callback)
53 preparedElem.addEventListener('loadstart', onLoadStart)
54 preparedElem.addEventListener('canplay', onCanPlay)
55
56 const wrapper = new MediaElementWrapper(preparedElem)
57 const writable = wrapper.createWriteStream(getCodec(file.name))
58 file.createReadStream().pipe(writable)
59
60 if (currentTime) preparedElem.currentTime = currentTime
61
62 return wrapper
63 }
64
65 function fallbackToMediaSource () {
66 preparedElem.removeEventListener('error', fallbackToMediaSource)
67 preparedElem.removeEventListener('canplay', onCanPlay)
68
69 useMediaSource()
70 }
71
72 function prepareElem () {
73 if (preparedElem === undefined) {
74 preparedElem = elem
75
76 preparedElem.addEventListener('progress', function () {
77 currentTime = elem.currentTime
78 })
79 }
80 }
81
82 function onLoadStart () {
83 preparedElem.removeEventListener('loadstart', onLoadStart)
84 if (opts.autoplay) preparedElem.play()
85 }
86
87 function onCanPlay () {
88 preparedElem.removeEventListener('canplay', onCanPlay)
89 callback(null, renderer)
90 }
91}
92
93function validateFile (file) {
94 if (file == null) {
95 throw new Error('file cannot be null or undefined')
96 }
97 if (typeof file.name !== 'string') {
98 throw new Error('missing or invalid file.name property')
99 }
100 if (typeof file.createReadStream !== 'function') {
101 throw new Error('missing or invalid file.createReadStream property')
102 }
103}
104
105function getCodec (name: string) {
106 const ext = extname(name).toLowerCase()
107 return {
108 '.m4a': 'audio/mp4; codecs="mp4a.40.5"',
109 '.m4v': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
110 '.mkv': 'video/webm; codecs="avc1.640029, mp4a.40.5"',
111 '.mp3': 'audio/mpeg',
112 '.mp4': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
113 '.webm': 'video/webm; codecs="vorbis, vp8"'
114 }[ext]
115}
116
117export {
118 renderVideo
119}