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.