PageSourceSearch

https://www.sedona.com/Resources/responsive/iphone-inline-video.js

js sedona.com collected 2026-09-26 02:55:35 UTC 10,601 bytes, 376 lines download raw bytes

1/*! npm.im/iphone-inline-video 2.0.2 */
2var enableInlineVideo = (function () {
3'use strict';
4
5/*! npm.im/intervalometer */
6function intervalometer(cb, request, cancel, requestParameter) {
7	var requestId;
8	var previousLoopTime;
9	function loop(now) {
10		// must be requested before cb() because that might call .stop()
11		requestId = request(loop, requestParameter);
12
13		// called with "ms since last call". 0 on start()
14		cb(now - (previousLoopTime || now));
15
16		previousLoopTime = now;
17	}
18	return {
19		start: function start() {
20			if (!requestId) { // prevent double starts
21				loop(0);
22			}
23		},
24		stop: function stop() {
25			cancel(requestId);
26			requestId = null;
27			previousLoopTime = 0;
28		}
29	};
30}
31
32function frameIntervalometer(cb) {
33	return intervalometer(cb, requestAnimationFrame, cancelAnimationFrame);
34}
35
36function preventEvent(element, eventName, toggleProperty, preventWithProperty) {
37	function handler(e) {
38		if (Boolean(element[toggleProperty]) === Boolean(preventWithProperty)) {
39			e.stopImmediatePropagation();
40			// console.log(eventName, 'prevented on', element);
41		}
42		delete element[toggleProperty];
43	}
44	element.addEventListener(eventName, handler, false);
45
46	// Return handler to allow to disable the prevention. Usage:
47	// const preventionHandler = preventEvent(el, 'click');
48	// el.removeEventHandler('click', preventionHandler);
49	return handler;
50}
51
52function proxyProperty(object, propertyName, sourceObject, copyFirst) {
53	function get() {
54		return sourceObject[propertyName];
55	}
56	function set(value) {
57		sourceObject[propertyName] = value;
58	}
59
60	if (copyFirst) {
61		set(object[propertyName]);
62	}
63
64	Object.defineProperty(object, propertyName, {get: get, set: set});
65}
66
67function proxyEvent(object, eventName, sourceObject) {
68	sourceObject.addEventListener(eventName, function () { return object.dispatchEvent(new Event(eventName)); });
69}
70
71function dispatchEventAsync(element, type) {
72	Promise.resolve().then(function () {
73		element.dispatchEvent(new Event(type));
74	});
75}
76
77var iOS8or9 = typeof document === 'object' && 'object-fit' in document.head.style && !matchMedia('(-webkit-video-playable-inline)').matches;
78
79var ಠ = 'bfred-it:iphone-inline-video';
80var ಠevent = 'bfred-it:iphone-inline-video:event';
81var ಠplay = 'bfred-it:iphone-inline-video:nativeplay';
82var ಠpause = 'bfred-it:iphone-inline-video:nativepause';
83
84/**
85 * UTILS
86 */
87
88function getAudioFromVideo(video) {
89	var audio = new Audio();
90	proxyEvent(video, 'play', audio);
91	proxyEvent(video, 'playing', audio);
92	proxyEvent(video, 'pause', audio);
93	audio.crossOrigin = video.crossOrigin;
94
95	// 'data:' causes audio.networkState > 0
96	// which then allows to keep <audio> in a resumable playing state
97	// i.e. once you set a real src it will keep playing if it was if .play() was called
98	audio.src = video.src || video.currentSrc || 'data:';
99
100	// if (audio.src === 'data:') {
101	//   TODO: wait for video to be selected
102	// }
103	return audio;
104}
105
106var lastRequests = [];
107var requestIndex = 0;
108var lastTimeupdateEvent;
109
110function setTime(video, time, rememberOnly) {
111	// allow one timeupdate event every 200+ ms
112	if ((lastTimeupdateEvent || 0) + 200 < Date.now()) {
113		video[ಠevent] = true;
114		lastTimeupdateEvent = Date.now();
115	}
116	if (!rememberOnly) {
117		video.currentTime = time;
118	}
119	lastRequests[++requestIndex % 3] = time * 100 | 0 / 100;
120}
121
122function isPlayerEnded(player) {
123	return player.driver.currentTime >= player.video.duration;
124}
125
126function update(timeDiff) {
127	var player = this;
128	// console.log('update', player.video.readyState, player.video.networkState, player.driver.readyState, player.driver.networkState, player.driver.paused);
129	if (player.video.readyState >= player.video.HAVE_FUTURE_DATA) {
130		if (!player.hasAudio) {
131			player.driver.currentTime = player.video.currentTime + ((timeDiff * player.video.playbackRate) / 1000);
132			if (player.video.loop && isPlayerEnded(player)) {
133				player.driver.currentTime = 0;
134			}
135		}
136		setTime(player.video, player.driver.currentTime);
137	} else if (player.video.networkState === player.video.NETWORK_IDLE && player.video.buffered.length === 0) {
138		// this should happen when the source is available but:
139		// - it's potentially playing (.paused === false)
140		// - it's not ready to play
141		// - it's not loading
142		// If it hasAudio, that will be loaded in the 'emptied' handler below
143		player.video.load();
144		// console.log('Will load');
145	}
146
147	// console.assert(player.video.currentTime === player.driver.currentTime, 'Video not updating!');
148
149	if (player.video.ended) {
150		delete player.video[ಠevent]; // allow timeupdate event
151		player.video.pause(true);
152	}
153}
154
155/**
156 * METHODS
157 */
158
159function play() {
160	// console.log('play');
161	var video = this;
162	var player = video[ಠ];
163
164	// if it's fullscreen, use the native player
165	if (video.webkitDisplayingFullscreen) {
166		video[ಠplay]();
167		return;
168	}
169
170	if (player.driver.src !== 'data:' && player.driver.src !== video.src) {
171		// console.log('src changed on play', video.src);
172		setTime(video, 0, true);
173		player.driver.src = video.src;
174	}
175
176	if (!video.paused) {
177		return;
178	}
179	player.paused = false;
180
181	if (video.buffered.length === 0) {
182		// .load() causes the emptied event
183		// the alternative is .play()+.pause() but that triggers play/pause events, even worse
184		// possibly the alternative is preventing this event only once
185		video.load();
186	}
187
188	player.driver.play();
189	player.updater.start();
190
191	if (!player.hasAudio) {
192		dispatchEventAsync(video, 'play');
193		if (player.video.readyState >= player.video.HAVE_ENOUGH_DATA) {
194			// console.log('onplay');
195			dispatchEventAsync(video, 'playing');
196		}
197	}
198}
199function pause(forceEvents) {
200	// console.log('pause');
201	var video = this;
202	var player = video[ಠ];
203
204	player.driver.pause();
205	player.updater.stop();
206
207	// if it's fullscreen, the developer the native player.pause()
208	// This is at the end of pause() because it also
209	// needs to make sure that the simulation is paused
210	if (video.webkitDisplayingFullscreen) {
211		video[ಠpause]();
212	}
213
214	if (player.paused && !forceEvents) {
215		return;
216	}
217
218	player.paused = true;
219	if (!player.hasAudio) {
220		dispatchEventAsync(video, 'pause');
221	}
222	if (video.ended) {
223		video[ಠevent] = true;
224		dispatchEventAsync(video, 'ended');
225	}
226}
227
228/**
229 * SETUP
230 */
231
232function addPlayer(video, hasAudio) {
233	var player = video[ಠ] = {};
234	player.paused = true; // track whether 'pause' events have been fired
235	player.hasAudio = hasAudio;
236	player.video = video;
237	player.updater = frameIntervalometer(update.bind(player));
238
239	if (hasAudio) {
240		player.driver = getAudioFromVideo(video);
241	} else {
242		video.addEventListener('canplay', function () {
243			if (!video.paused) {
244				// console.log('oncanplay');
245				dispatchEventAsync(video, 'playing');
246			}
247		});
248		player.driver = {
249			src: video.src || video.currentSrc || 'data:',
250			muted: true,
251			paused: true,
252			pause: function () {
253				player.driver.paused = true;
254			},
255			play: function () {
256				player.driver.paused = false;
257				// media automatically goes to 0 if .play() is called when it's done
258				if (isPlayerEnded(player)) {
259					setTime(video, 0);
260				}
261			},
262			get ended() {
263				return isPlayerEnded(player);
264			}
265		};
266	}
267
268	// .load() causes the emptied event
269	video.addEventListener('emptied', function () {
270		// console.log('driver src is', player.driver.src);
271		var wasEmpty = !player.driver.src || player.driver.src === 'data:';
272		if (player.driver.src && player.driver.src !== video.src) {
273			// console.log('src changed to', video.src);
274			setTime(video, 0, true);
275			player.driver.src = video.src;
276			// playing videos will only keep playing if no src was present when .play()’ed
277			if (wasEmpty) {
278				player.driver.play();
279			} else {
280				player.updater.stop();
281			}
282		}
283	}, false);
284
285	// stop programmatic player when OS takes over
286	video.addEventListener('webkitbeginfullscreen', function () {
287		if (!video.paused) {
288			// make sure that the <audio> and the syncer/updater are stopped
289			video.pause();
290
291			// play video natively
292			video[ಠplay]();
293		} else if (hasAudio && player.driver.buffered.length === 0) {
294			// if the first play is native,
295			// the <audio> needs to be buffered manually
296			// so when the fullscreen ends, it can be set to the same current time
297			player.driver.load();
298		}
299	});
300	if (hasAudio) {
301		video.addEventListener('webkitendfullscreen', function () {
302			// sync audio to new video position
303			player.driver.currentTime = video.currentTime;
304			// console.assert(player.driver.currentTime === video.currentTime, 'Audio not synced');
305		});
306
307		// allow seeking
308		video.addEventListener('seeking', function () {
309			if (lastRequests.indexOf(video.currentTime * 100 | 0 / 100) < 0) {
310				// console.log('User-requested seeking');
311				player.driver.currentTime = video.currentTime;
312			}
313		});
314	}
315}
316
317function overloadAPI(video) {
318	var player = video[ಠ];
319	video[ಠplay] = video.play;
320	video[ಠpause] = video.pause;
321	video.play = play;
322	video.pause = pause;
323	proxyProperty(video, 'paused', player.driver);
324	proxyProperty(video, 'muted', player.driver, true);
325	proxyProperty(video, 'playbackRate', player.driver, true);
326	proxyProperty(video, 'ended', player.driver);
327	proxyProperty(video, 'loop', player.driver, true);
328	preventEvent(video, 'seeking');
329	preventEvent(video, 'seeked');
330	preventEvent(video, 'timeupdate', ಠevent, false);
331	preventEvent(video, 'ended', ಠevent, false); // prevent occasional native ended events
332}
333
334function enableInlineVideo(video, opts) {
335	if ( opts === void 0 ) opts = {};
336
337	// Stop if already enabled
338	if (video[ಠ]) {
339		return;
340	}
341
342	// Allow the user to skip detection
343	if (!opts.everywhere) {
344		// Only iOS8 and 9 are supported
345		if (!iOS8or9) {
346			return;
347		}
348
349		// Stop if it's not an allowed device
350		if (!(opts.iPad || opts.ipad ? /iPhone|iPod|iPad/ : /iPhone|iPod/).test(navigator.userAgent)) {
351			return;
352		}
353	}
354
355	// Stop native playback
356	if (!video.paused && video.webkitDisplayingFullscreen) {
357		video.pause();
358	}
359
360	addPlayer(video, !video.muted);
361	overloadAPI(video);
362	video.classList.add('IIV');
363
364	// Autoplay
365	if (video.muted && video.autoplay) {
366		video.play();
367	}
368
369	if (!/iPhone|iPod|iPad/.test(navigator.platform)) {
370		console.warn('iphone-inline-video is not guaranteed to work in emulated environments');
371	}
372}
373
374return enableInlineVideo;
375
376}());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.