Don't play video if user paused it during loading
[oweals/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     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
93 function 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
105 function 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
117 export {
118   renderVideo
119 }