|
2 | 2 | import type { Models } from '../../types/models'; |
3 | 3 | import type { HTMLMicrioElement } from '../../ts/element'; |
4 | 4 | import { writable, type Unsubscriber } from 'svelte/store'; |
5 | | - import type { HlsPlayer } from '../../types/externals'; |
6 | 5 |
|
7 | | - import { onMount, getContext, tick } from 'svelte'; |
| 6 | + import { onMount, getContext } from 'svelte'; |
8 | 7 | import { MicrioImage } from '../../ts/image'; |
9 | | - import { loadScript, once, createGUID, hasNativeHLS, Browser } from '../../ts/utils'; |
| 8 | + import { once, createGUID, Browser } from '../../ts/utils'; |
| 9 | + import { GLEmbedVideo } from '../../ts/embedvideo'; |
10 | 10 |
|
11 | 11 | import Media from '../components/Media.svelte'; |
12 | 12 |
|
|
122 | 122 | if((embed.video?.pauseWhenSmallerThan || embed.video?.pauseWhenLargerThan) && width) { |
123 | 123 | const wasPaused:boolean = paused; |
124 | 124 | paused = shouldPause(); |
125 | | - if(_vid && printGL && (paused != wasPaused)) { |
126 | | - if(paused) _vid.pause(); |
| 125 | + if(glVideo?._vid && (paused != wasPaused)) { |
| 126 | + if(paused) glVideo._vid.pause(); |
127 | 127 | else { |
128 | | - if(mainImage?.$settings?.embedRestartWhenShown) _vid.currentTime = 0; |
129 | | - _vid.play(); |
| 128 | + if(mainImage?.$settings?.embedRestartWhenShown) glVideo._vid.currentTime = 0; |
| 129 | + glVideo._vid.play(); |
130 | 130 | } |
131 | 131 | } |
132 | 132 | } |
|
151 | 151 | // If embed has ID of marker, watch if marker is closed to do media pre-destroy |
152 | 152 | const destroying = writable<boolean>(false); |
153 | 153 |
|
154 | | - // WebGL-embedded video |
155 | | - let hlsPlayer: HlsPlayer|undefined = undefined; |
156 | | - let usVid:Unsubscriber|undefined = undefined; |
157 | | - let _vid:HTMLVideoElement|undefined = image?._video; |
158 | | - let vidRepeatTo:any = undefined; |
159 | | -
|
160 | 154 | if(embed.video) { |
161 | 155 | if(!embed.video.controls) embed.video.muted = true; |
162 | 156 | } |
163 | 157 |
|
164 | | - const setWebGLVideoPlaying = (playing:boolean) : void => { |
165 | | - if(!_vid) return; |
166 | | - if(!isMounted) playing = false; |
167 | | - _vid.dataset.playing = playing ? '1' : undefined; |
168 | | - wasm.e._setImageVideoPlaying(image.ptr, playing); |
169 | | - if(embed.hideWhenPaused) wasm.fadeImage(image.ptr, playing ? 1 : 0); |
170 | | - if(playing) wasm.render(); |
171 | | - } |
172 | | -
|
173 | | - function loadWebGLVideo() : void { |
174 | | - if(!embed.video) return; |
175 | | -
|
176 | | - // Cloudflare stream doesn't support alpha transparent videos yet, |
177 | | - // so use the original src if transparency is set to true. |
178 | | - const ism3u = !!embed.video.streamId && !embed.video.transparent; |
179 | | - const src = ism3u ? `https://videodelivery.net/${embed.video.streamId}/manifest/video.m3u8` : embed.video.src; |
180 | | - _vid = document.createElement('video'); |
181 | | - _vid.crossOrigin = 'true'; |
182 | | - _vid.playsInline = true; |
183 | | - _vid.width = embed.width! * .5; |
184 | | - _vid.height = embed.height! * .5; |
185 | | - _vid.muted = embed.video.muted; |
186 | | - if($current && embed.id) $current.setEmbedMediaElement(embed.id, _vid); |
187 | | -
|
188 | | - const loopAfter = embed.video.loopAfter; |
189 | | - if(embed.video.loop && loopAfter) { |
190 | | - _vid.onended = () => { |
191 | | - setWebGLVideoPlaying(false); |
192 | | - vidRepeatTo = <any>setTimeout(() => _vid?.play(), loopAfter * 1000) as number; |
193 | | - } |
194 | | - _vid.onplay = () => setWebGLVideoPlaying(true); |
195 | | - } |
196 | | - else _vid.loop = embed.video.loop; |
197 | | -
|
198 | | - // If no autoplay, has to be rendered in DOM for first frame visibility |
199 | | - if(!autoplay && !ism3u) { |
200 | | - _vid.setAttribute('style','opacity:0;position:absolute;top:0;left:0;transform-origin:left top;transform:scale(0.1);pointer-events:none;'); |
201 | | - document.body.appendChild(_vid); |
202 | | - } |
203 | | -
|
204 | | - _vid.addEventListener('play', () => setWebGLVideoPlaying(!paused)); |
205 | | - _vid.addEventListener('pause', () => setWebGLVideoPlaying(false)); |
206 | | -
|
207 | | - // Only on first frame drawn, print the video |
208 | | - _vid.addEventListener('playing', () => image.video.set(_vid), {once:true}); |
209 | | -
|
210 | | - // OF COURSE certain iOS versions (iPhone 13..) don't fire the canplay-event |
211 | | - _vid.addEventListener(Browser.iOS ? 'loadedmetadata' : 'canplay', () => { |
212 | | - if(!_vid || !isMounted) return; |
213 | | - // It could already be paused by scale limiting |
214 | | - if(autoplay && !paused) { |
215 | | - _vid.play(); |
216 | | - moved(); |
217 | | - } |
218 | | - else if(!embed.hideWhenPaused) { // Show first frame |
219 | | - setWebGLVideoPlaying(true); |
220 | | - tick().then(() => { |
221 | | - setWebGLVideoPlaying(false); |
222 | | - setTimeout(() => _vid?.remove(),50); |
223 | | - }) |
224 | | - } |
225 | | - }, {once: true}); |
226 | | -
|
227 | | - if(!ism3u || hasNativeHLS(_vid)) _vid.src = src; |
228 | | - else loadScript('https://i.micr.io/hls-1.5.17.min.js', undefined, 'Hls' in window ? {} : undefined).then(() => { |
229 | | - /** @ts-ignore */ |
230 | | - hlsPlayer = new (window['Hls'] as HlsPlayer)(); |
231 | | - hlsPlayer.loadSource(src); |
232 | | - if(_vid) hlsPlayer.attachMedia(_vid); |
233 | | - }); |
234 | | - } |
235 | | -
|
236 | | - let inScreen:boolean = false; |
237 | | -
|
238 | | - function printWebGLVideo() : void { |
239 | | - let to:any; |
240 | | - let first:boolean = true; |
241 | | - usVid = image.visible.subscribe(v => { |
242 | | - clearTimeout(to); |
243 | | - inScreen = v; |
244 | | - if(v) to = setTimeout(() => { |
245 | | - if(!isMounted) return; |
246 | | - if(!_vid) loadWebGLVideo(); |
247 | | - else if(autoplay) _vid.play(); |
248 | | - }, first ? 0 : 100); |
249 | | - else to = setTimeout(() => _vid?.pause(), 0); |
250 | | - first = false; |
251 | | - }) |
252 | | - } |
253 | | -
|
254 | 158 | const isRawVideo = printGL && !!embed.video; |
| 159 | + let glVideo:GLEmbedVideo|undefined = undefined; |
255 | 160 | function printInsideGL() : void { |
256 | 161 | const opacity = embed.hideWhenPaused ? 0.01 : embed.opacity || 1; |
257 | 162 | if(image && image.ptr >= 0) { |
|
278 | 183 | }, embed.area, { opacity, asImage: false }); |
279 | 184 | } |
280 | 185 |
|
281 | | - if(isRawVideo) once(image.visible, {targetValue: true}).then(() => printWebGLVideo()); |
| 186 | + if(isRawVideo) once(image.visible, {targetValue: true}).then(() => { |
| 187 | + // This takes care of loading and playing |
| 188 | + glVideo = new GLEmbedVideo(wasm, image, embed, paused, moved) |
| 189 | + }); |
282 | 190 |
|
283 | 191 | wasm.render(); |
284 | 192 | } |
|
288 | 196 |
|
289 | 197 | // For <Media /> video embeds, set the embed.video.element on availability |
290 | 198 | $: { |
291 | | - if(embed.video && embed.id && $current) $current.setEmbedMediaElement(embed.id, _mediaElement); |
| 199 | + if(embed.video && embed.id && $current) $current.setEmbedMediaElement(embed.id, _mediaElement??glVideo?._vid); |
292 | 200 | } |
293 | 201 |
|
294 | 202 | // Cap the max <video> element width/height to original video resolution |
|
306 | 214 |
|
307 | 215 | return () => { |
308 | 216 | isMounted = false; |
| 217 | + glVideo?.unmount(); |
309 | 218 | if(image) { |
310 | | - clearTimeout(vidRepeatTo); |
311 | | - if(usVid) usVid(); |
312 | 219 | wasm.fadeImage(image.ptr, 0); |
313 | 220 | wasm.render(); |
314 | 221 | } |
315 | | - if(_vid) _vid.pause(); |
316 | 222 | if(embed.video && embed.id && $current) $current.setEmbedMediaElement(embed.id); |
317 | 223 | while(us.length) us.shift()?.(); |
318 | 224 | } |
|
0 commit comments