Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,4 @@
;(function(exports){'use strict';function Captions(player){this.player=player;}
Captions.prototype.remove=function(){for(var track of this.player.querySelectorAll('track')){if(track.src&&track.src.startsWith('blob:')){URL.revokeObjectURL(track.src);}
track.remove();}};Captions.prototype.findAndDisplay=function(filepath){if(!filepath){return;}
var basename=filepath.substring(0,filepath.lastIndexOf('.'));var captionsFilename=basename+'.vtt';var sdcard=navigator.getDeviceStorage('sdcard');if(!sdcard)return;var request=sdcard.get(captionsFilename);request.onsuccess=function(){var track=document.createElement('track');track.src=URL.createObjectURL(request.result);track.default=true;this.player.appendChild(track);track.track.mode='showing';}.bind(this);};exports.Captions=Captions;}(window));

View file

@ -0,0 +1,13 @@
;'use strict';function initDB(){videodb=new MediaDB('videos',null,{excludeFilter:/DCIM\/\d{3}MZLLA\/\.VID_\d{4}\.3gp$/});videodb.onupgrading=function(evt){storageState=MediaDB.UPGRADING;updateDialog();};videodb.onunavailable=function(event){storageState=event.detail;if(playerShowing){hidePlayer(true);}
updateDialog();};videodb.oncardremoved=function(){if(playerShowing){hidePlayer(true);}};videodb.onscanend=function(){if(!firstScanEnded){firstScanEnded=true;window.performance.mark('fullyLoaded');}
updateDialog();updateLoadingSpinner();};videodb.oncreated=function(event){event.detail.forEach(videoCreated);};videodb.ondeleted=function(event){event.detail.forEach(videoDeleted);};videodb.onenumerable=function(){storageState=false;updateDialog();enumerateDB();};videodb.onready=function(){storageState=false;updateDialog();startParsingMetadata();};}
var enumerated=false;function enumerateDB(){if(enumerated){return;}
enumerated=true;var firstBatchDisplayed=false;var batch=[];var batchSize=4;videodb.enumerate('date',null,'prev',function(videoinfo){if(videoinfo===null){flush();return;}
var isVideo=videoinfo.metadata.isVideo;if(isVideo===false){return;}
if(isVideo===undefined){addToMetadataQueue(videoinfo);return;}
if(isVideo===true){batch.push(videoinfo);if(batch.length>=batchSize){flush();batchSize*=2;}}});function flush(){batch.forEach(addVideo);batch.length=0;if(!firstBatchDisplayed){firstBatchDisplayed=true;window.performance.mark('visuallyLoaded');window.performance.mark('contentInteractive');setTimeout(function(){var headers=document.querySelectorAll('gaia-header');for(var i=0;i<headers.length;++i){headers[i].removeAttribute('no-font-fit');}});}}}
function addVideo(videodata){if(!videodata||!videodata.metadata.isVideo){return;}
var view=thumbnailList.addItem(videodata);view.addTapListener(thumbnailClickHandler);view.updateTitleText();if(thumbnailList.count===1){updateDialog();}}
function videoCreated(videoinfo){addToMetadataQueue(videoinfo);}
function videoDeleted(filename){if(currentVideo&&filename===currentVideo.name){resetCurrentVideo();}
thumbnailList.removeItem(filename);if(thumbnailList.count===0){updateDialog();hideSelectView();}}

View file

@ -0,0 +1,12 @@
;'use strict';var ForwardRewindController=(function(){var isLongPressing=false;var intervalId=null;var forwardButton;var backwardButton;var videoToolbar;var player;var seekHelper;function init(video,forward,backward,seeker){forwardButton=forward;backwardButton=backward;player=video;seekHelper=seeker;videoToolbar=forward.parentElement;videoToolbar.addEventListener('click',handleButtonClick);videoToolbar.addEventListener('contextmenu',handlePlayerLongPress);videoToolbar.addEventListener('touchend',stopFastSeeking);}
function uninit(video,forward,backward){videoToolbar.removeEventListener('click',handleButtonClick);videoToolbar.removeEventListener('contextmenu',handlePlayerLongPress);videoToolbar.removeEventListener('touchend',stopFastSeeking);forwardButton=null;backwardButton=null;player=null;}
function handleButtonClick(event){if(event.target===forwardButton){startFastSeeking(1);}else if(event.target===backwardButton){startFastSeeking(-1);}else{return;}}
function handlePlayerLongPress(event){if(event.target===forwardButton){isLongPressing=true;startFastSeeking(1);}else if(event.target===backwardButton){isLongPressing=true;startFastSeeking(-1);}else{return;}}
function startFastSeeking(direction){var offset=direction*10;if(isLongPressing){intervalId=window.setInterval(function(){seekVideo(player.currentTime+offset);},1000);}else{seekVideo(player.currentTime+offset);}}
function stopFastSeeking(){if(intervalId){window.clearInterval(intervalId);intervalId=null;isLongPressing=false;}}
function seekVideo(seekTime){if(seekTime>=player.duration||seekTime<0){if(isLongPressing){stopFastSeeking();}
if(seekTime>=player.duration){seekTime=player.duration;pause();}
else{seekTime=0;}}
if(seekHelper){seekHelper.seekTo(seekTime);}
else{player.fastSeek(seekTime);}}
return{init:init,uninit:uninit,stopFastSeeking:stopFastSeeking};}());

View file

@ -0,0 +1,30 @@
;'use strict';var metadataQueue=[];var processingQueue=false;var stopParsingMetadataCallback=null;var noMoreWorkCallback=null;function addToMetadataQueue(fileinfo){metadataQueue.push(fileinfo);startParsingMetadata();}
function startParsingMetadata(){if(processingQueue){return;}
if(metadataQueue.length===0){if(noMoreWorkCallback){noMoreWorkCallback();noMoreWorkCallback=null;}
return;}
if(document.hidden||playerShowing){return;}
if(videodb.state!==MediaDB.READY){return;}
processingQueue=true;showThrobber();processFirstQueuedItem();}
function stopParsingMetadata(callback){if(!processingQueue){if(callback){callback();}
return;}
stopParsingMetadataCallback=callback||true;}
function processFirstQueuedItem(){if(stopParsingMetadataCallback){var callback=stopParsingMetadataCallback;stopParsingMetadataCallback=null;processingQueue=false;hideThrobber();if(callback!==true){callback();}
return;}
if(metadataQueue.length===0){processingQueue=false;hideThrobber();updateDialog();if(typeof(noMoreWorkCallback)==='function'){noMoreWorkCallback();}
return;}
var fileinfo=metadataQueue.shift();videodb.getFile(fileinfo.name,function(file){getMetadata(file,function(metadata){fileinfo.metadata=metadata;videodb.updateMetadata(fileinfo.name,metadata,function(){if(metadata.isVideo){videodb.getFileInfo(fileinfo.name,function(dbfileinfo){addVideo(dbfileinfo);});}});setTimeout(processFirstQueuedItem);});},function(err){console.error('getFile error: ',fileinfo.name,err);processFirstQueuedItem();});}
function getMetadata(videofile,callback){var offscreenVideo=document.createElement('video');var metadata={};if(!offscreenVideo.canPlayType(videofile.type)){metadata.isVideo=false;callback(metadata);return;}
var url=URL.createObjectURL(videofile);offscreenVideo.preload='metadata';offscreenVideo.src=url;const METADATA_TIMEOUT=3000;var timeoutId=setTimeout(function(){console.error('No loadedmetadata or error events seen yet.','Assuming video file is corrupt:',videofile.name);offscreenVideo.onerror();},METADATA_TIMEOUT);offscreenVideo.onerror=function(e){console.error('Can\'t play video',videofile.name,e);metadata.isVideo=false;clearTimeout(timeoutId);unload();callback(metadata);};offscreenVideo.onloadedmetadata=function(){clearTimeout(timeoutId);if(!offscreenVideo.videoWidth){metadata.isVideo=false;unload();callback(metadata);return;}
metadata.isVideo=true;metadata.title=readFromMetadata('title')||fileNameToVideoName(videofile.name);metadata.duration=offscreenVideo.duration;metadata.width=offscreenVideo.videoWidth;metadata.height=offscreenVideo.videoHeight;if(/.3gp$/.test(videofile.name)){getVideoRotation(videofile,function(rotation){if(typeof rotation==='number'){metadata.rotation=rotation;}
else if(typeof rotation==='string'){console.warn('Video rotation:',rotation);}
createThumbnail();});}else{metadata.rotation=0;createThumbnail();}};function readFromMetadata(lowerCaseKey){var tags=offscreenVideo.mozGetMetadata();for(var key in tags){if(key.toLowerCase()===lowerCaseKey){return tags[key];}}
return;}
function createThumbnail(){var t=Math.min(5,offscreenVideo.duration/10);offscreenVideo.fastSeek(t);var failed=false;var timeout=setTimeout(fail,10000);offscreenVideo.onerror=fail;function fail(){console.warn('Seek failed while creating thumbnail for',videofile.name,'. Ignoring corrupt file.');failed=true;clearTimeout(timeout);offscreenVideo.onerror=null;metadata.isVideo=false;unload();callback(metadata);}
offscreenVideo.onseeked=function(){if(failed){return;}
clearTimeout(timeout);captureFrame(offscreenVideo,metadata,function(poster){metadata.poster=poster;unload();callback(metadata);});};}
function unload(){URL.revokeObjectURL(offscreenVideo.src);offscreenVideo.removeAttribute('src');offscreenVideo.load();}
function fileNameToVideoName(filename){filename=filename.split('/').pop().replace(/\.(webm|ogv|ogg|mp4|3gp)$/,'').replace(/[_\.]/g,' ');return filename.charAt(0).toUpperCase()+filename.slice(1);}}
function captureFrame(player,metadata,callback){try{var canvas=document.createElement('canvas');canvas.width=THUMBNAIL_WIDTH;canvas.height=THUMBNAIL_HEIGHT;var ctx=canvas.getContext('2d',{willReadFrequently:true});var vw=player.videoWidth,vh=player.videoHeight;var tw,th;switch(metadata.rotation){case 90:ctx.translate(THUMBNAIL_WIDTH,0);ctx.rotate(Math.PI/2);tw=THUMBNAIL_HEIGHT;th=THUMBNAIL_WIDTH;break;case 180:ctx.translate(THUMBNAIL_WIDTH,THUMBNAIL_HEIGHT);ctx.rotate(Math.PI);tw=THUMBNAIL_WIDTH;th=THUMBNAIL_HEIGHT;break;case 270:ctx.translate(0,THUMBNAIL_HEIGHT);ctx.rotate(-Math.PI/2);tw=THUMBNAIL_HEIGHT;th=THUMBNAIL_WIDTH;break;default:tw=THUMBNAIL_WIDTH;th=THUMBNAIL_HEIGHT;break;}
var scale=Math.min(tw/vw,th/vh),w=scale*vw,h=scale*vh,x=(tw-w)/2/scale,y=(th-h)/2/scale;ctx.scale(scale,scale);ctx.drawImage(player,x,y);canvas.toBlob(done,'image/jpeg');}
catch(e){console.error('Exception in captureFrame:',e,e.stack);done(null);}
function done(blob){canvas.width=0;ctx=canvas=null;callback(blob);}}

View file

@ -0,0 +1,5 @@
;(function(exports){'use strict';function Seeker(player){this.player=player;this.seekStartedAt=null;this.currentSeekTime=null;this.nextSeekTime=null;this.player.addEventListener('seeked',this);}
Seeker.SEEK_INTERVAL=200;Seeker.SEEK_DISTANCE=10;Seeker.prototype.seekTo=function(time){if(this.currentSeekTime===time){return;}
var now=performance.now();if(this.currentSeekTime===null||time===0||Math.abs(this.currentSeekTime-time)>Seeker.SEEK_DISTANCE||now-this.seekStartedAt>Seeker.SEEK_INTERVAL)
{this.player.fastSeek(time);this.seekStartedAt=now;this.currentSeekTime=time;this.nextSeekTime=null;}
else{this.nextSeekTime=time;}};Seeker.prototype.handleEvent=function(){this.currentSeekTime=null;this.seekStartedAt=null;if(this.nextSeekTime!==null){this.seekTo(this.nextSeekTime);}};exports.Seeker=Seeker;}(window));

View file

@ -0,0 +1,15 @@
;'use strict';function ThumbnailDateGroup(item){if(!item){throw new Error('item should not be null or undefined.');}
this.thumbnails=[];this.groupID=ThumbnailDateGroup.getGroupID(item);this.date=item.date;var wrappedHtmlText=ThumbnailDateGroup.view();var dummyDiv=document.createElement('DIV');dummyDiv.innerHTML=Sanitizer.unwrapSafeHTML(wrappedHtmlText);var domNode=dummyDiv.firstElementChild;if(!domNode){throw new Error('the template is empty');}
this.htmlNode=domNode;this.container=domNode.querySelector('.thumbnail-group-container');this.header=domNode.querySelector('.thumbnail-group-header');this.localize();}
ThumbnailDateGroup.view=function(){return Sanitizer.createSafeHTML`<li role="presentation">
<div class="thumbnail-group-header" role="heading"
aria-level="1"></div>
<ul class="thumbnail-group-container" role="listbox"
data-l10n-id="videos-list" aria-multiselectable="true"></ul>
</li>`;};ThumbnailDateGroup.getGroupID=function(item){var dateObj=new Date(item.date);var month=dateObj.getMonth()+1;return'group_'+dateObj.getFullYear()+'-'+
(month<10?'0'+month:month);};ThumbnailDateGroup.compareGroupID=function(id1,id2){return id1>id2?1:(id1<id2?-1:0);};ThumbnailDateGroup.prototype.addItem=function(item){if(!item){return;}
var _this=this;function getInsertPosition(thumbnail){if(_this.thumbnails.length===0||thumbnail.data.date>_this.thumbnails[0].data.date){return 0;}
else if(thumbnail.data.date<_this.thumbnails[_this.thumbnails.length-1].data.date){return _this.thumbnails.length;}
else{return MediaUtils.binarySearch(_this.thumbnails,thumbnail,function(a,b){return b.data.date-a.data.date;});}}
var thumbnail=new ThumbnailItem(item);var insertPosition=getInsertPosition(thumbnail);this.container.insertBefore(thumbnail.htmlNode,this.container.children[insertPosition]);this.thumbnails.splice(insertPosition,0,thumbnail);return thumbnail;};ThumbnailDateGroup.prototype.getCount=function(){return this.thumbnails.length;};ThumbnailDateGroup.prototype.removeItem=function(thumbnail){var idx=this.thumbnails.indexOf(thumbnail);if(idx<0){return;}
this.thumbnails.splice(idx,1);this.container.removeChild(thumbnail.htmlNode);};ThumbnailDateGroup.prototype.localize=function(){var date=new Date(this.date);var formatter=IntlHelper.get('date-group');this.header.textContent=formatter.format(date);this.thumbnails.forEach(function(thumbnail){thumbnail.localize();});};

View file

@ -0,0 +1,27 @@
;'use strict';function ThumbnailItem(videoData){if(!videoData){throw new Error('videoData should not be null or undefined.');}
this.posterNode=null;this.unwatchedNode=null;this.detailNode=null;this.titleNode=null;this.htmlNode=null;this.tapListeners=[];this.data=videoData;var _this=this;render();function convertToDOM(wrappedHTML){var dummyDiv=document.createElement('div');dummyDiv.innerHTML=Sanitizer.unwrapSafeHTML(wrappedHTML);var domNode=dummyDiv.firstElementChild;if(!domNode){throw new Error('the template does not contain any element');}
_this.htmlNode=domNode;_this.posterNode=domNode.querySelector('.img');_this.detailNode=domNode.querySelector('.details');_this.titleNode=domNode.querySelector('.title');_this.unwatchedNode=domNode.querySelector('.unwatched');_this.sizeNode=domNode.querySelector('.size-text');}
function render(){var duration='';if(isFinite(_this.data.metadata.duration)){duration=MediaUtils.formatDuration(_this.data.metadata.duration);}
var videoType='';if(_this.data.type){var pos=_this.data.type.indexOf('/');videoType=(pos>-1?_this.data.type.slice(pos+1):_this.data.type);}
var wrappedHtmlText=ThumbnailItem.view({'title':_this.data.metadata.title,'durationText':duration,'typeText':videoType});convertToDOM(wrappedHtmlText);_this.updatePoster(_this.data.metadata.bookmark||_this.data.metadata.poster);_this.setWatched(_this.data.metadata.watched);if(_this.detailNode){_this.detailNode.dataset.title=_this.data.metadata.title;}
_this.htmlNode.addEventListener('click',dispatchClick);_this.localize();}
function dispatchClick(){_this.tapListeners.forEach(function(listener){if(listener.handleEvent){listener.handleEvent(_this.data);}else if((typeof listener)==='function'){listener(_this.data);}});}}
ThumbnailItem.titleMaxLines=2;ThumbnailItem.view=function({title,durationText,typeText}){return Sanitizer.createSafeHTML`<li class="thumbnail" role="option">
<div class="inner">
<div class="unwatched"></div>
<img class="img" role="presentation"></img>
<div class="details">
<span class="title">${title}</span>
<span class="duration-text after line-break">${durationText}</span>
<span class="size-type-group">
<span class="size-text after"></span>
<span class="type-text after">${typeText}</span>
</span>
</div>
</div>
</li>`;};ThumbnailItem.prototype.addTapListener=function(listener){if(!listener){return;}
this.tapListeners[this.tapListeners.length]=listener;};ThumbnailItem.prototype.removeTapListener=function(listener){if(!listener){return;}
var idx=this.tapListeners.indexOf(listener);if(idx>-1){this.tapListeners.splice(idx,1);}};ThumbnailItem.prototype.setWatched=function(watched){if(!this.unwatchedNode){return;}
this.unwatchedNode.hidden=watched;};ThumbnailItem.prototype.updatePoster=function(imageblob){if(!this.posterNode){return;}
if(this.posterNode.dataset.uri){URL.revokeObjectURL(this.posterNode.dataset.uri);}
if(imageblob){this.posterNode.classList.remove('default');var imageUri=URL.createObjectURL(imageblob);this.posterNode.dataset.uri=imageUri;this.posterNode.src=imageUri;}else{this.posterNode.classList.add('default');this.posterNode.dataset.uri='';this.posterNode.src='style/images/default_thumbnail.png';}};ThumbnailItem.prototype.updateTitleText=function(){this.titleNode.textContent=VideoUtils.getTruncated(this.data.metadata.title,{node:this.titleNode,maxLine:ThumbnailItem.titleMaxLines});};ThumbnailItem.prototype.localize=function(){if(this.sizeNode&&isFinite(this.data.size)){MediaUtils.getLocalizedSizeTokens(this.data.size).then((args)=>{document.l10n.setAttributes(this.sizeNode,'fileSize',args);});}};

View file

@ -0,0 +1,16 @@
;'use strict';function ThumbnailList(groupClass,container){if(!groupClass||!container){throw new Error('group class or container cannot be null or undefined');}
this.thumbnailMap={};this.groupMap={};this.itemGroups=[];this.count=0;this.groupClass=groupClass;this.container=container;document.addEventListener('DOMRetranslated',this.localize.bind(this));}
ThumbnailList.prototype.addItem=function(item){if(!item){return null;}
if(this.thumbnailMap[item.name]){return this.thumbnailMap[item.name];}
var _this=this;function createItemGroup(item,before){var group=new _this.groupClass(item);_this.container.insertBefore(group.htmlNode,before?before.htmlNode:null);return group;}
function getItemGroup(item){var groupID=_this.groupClass.getGroupID(item);var i;for(i=0;i<_this.itemGroups.length;i++){if(_this.itemGroups[i].groupID===groupID){return _this.itemGroups[i];}else if(_this.groupClass.compareGroupID(_this.itemGroups[i].groupID,groupID)<0){break;}}
var createdGroup=createItemGroup(item,_this.itemGroups[i]);_this.itemGroups.splice(i,0,createdGroup);return createdGroup;}
var group=getItemGroup(item);var thumbnail=group.addItem(item);this.groupMap[item.name]=group;this.thumbnailMap[item.name]=thumbnail;this.count++;return thumbnail;};ThumbnailList.prototype.removeItem=function(filename){if(!this.thumbnailMap[filename]){return;}
var group=this.groupMap[filename];group.removeItem(this.thumbnailMap[filename]);if(!group.getCount()){this.container.removeChild(group.htmlNode);this.itemGroups.splice(this.itemGroups.indexOf(group),1);}
this.count--;delete this.groupMap[filename];delete this.thumbnailMap[filename];};ThumbnailList.prototype.reset=function(){for(var name in this.thumbnailMap){this.groupMap[name].removeItem(this.thumbnailMap[name]);}
this.container.innerHTML='';this.thumbnailMap={};this.itemGroups=[];this.groupMap={};this.count=0;};ThumbnailList.prototype.isPickMode=function(){return this.container.classList.contains('pick');};ThumbnailList.prototype.setPickMode=function(pick){if(pick){this.container.classList.add('pick');}else{this.container.classList.remove('pick');}};ThumbnailList.prototype.isSelectMode=function(){return this.container.classList.contains('select');};ThumbnailList.prototype.setSelectMode=function(select){if(select){this.container.classList.add('select');}else{this.container.classList.remove('select');}};ThumbnailList.prototype.updateAllThumbnailTitles=function(){var self=this;if(this.pendingTitleTextUpdate&&this.pendingTitleTextUpdate.length){this.pendingTitleTextUpdate.length=0;}
var thumbnailKeys=Object.keys(this.thumbnailMap);this.pendingTitleTextUpdate=thumbnailKeys;setTimeout(updateNextThumbnailBatch);function updateNextThumbnailBatch(){for(var i=0;i<10;i++){if(thumbnailKeys.length===0){return;}
var nextKey=thumbnailKeys.shift();self.thumbnailMap[nextKey].updateTitleText();}
setTimeout(updateNextThumbnailBatch);}};ThumbnailList.prototype.findNextThumbnail=function(filename){var currentGroup=this.groupMap[filename];if(!currentGroup){return this.itemGroups.length?this.itemGroups[0].thumbnails[0]:null;}
var currentThumbnail=this.thumbnailMap[filename];var idx;if(currentGroup.thumbnails.length===1){idx=this.itemGroups.indexOf(currentGroup);if(idx===0){if(this.itemGroups.length>1){currentThumbnail=this.itemGroups[1].thumbnails[0];}else{currentThumbnail=null;}}else{var thumbnails=this.itemGroups[idx-1].thumbnails;currentThumbnail=thumbnails[thumbnails.length-1];}}else{idx=currentGroup.thumbnails.indexOf(currentThumbnail);if(idx===0){currentThumbnail=currentGroup.thumbnails[1];}else{currentThumbnail=currentGroup.thumbnails[idx-1];}}
return currentThumbnail;};ThumbnailList.prototype.localize=function(){this.itemGroups.forEach(function(group){group.localize();});};

View file

@ -0,0 +1,131 @@
;'use strict';const LAYOUT_MODE={list:'layout-list',selection:'layout-selection',fullscreenPlayer:'layout-fullscreen-player'};var dom={};var ids=['thumbnail-list-title','thumbnails','thumbnails-video-button','thumbnails-select-button','thumbnails-delete-button','thumbnails-share-button','thumbnails-select-top','thumbnails-number-selected','player-view','fullscreen-button','spinner-overlay','thumbnails-single-delete-button','thumbnails-single-share-button','thumbnails-single-info-button','info-view','info-close-button','player','overlay','overlay-title','overlay-text','overlay-menu','overlay-action-button','player-header','video-container','videoBar','videoControlBar','close','play','playHead','timeSlider','elapsedTime','video-title','duration-text','elapsed-text','bufferedTime','slider-wrapper','throbber','picker-close','picker-title','picker-header','picker-done','options','options-view','seek-backward','seek-forward','in-use-overlay','in-use-overlay-title','in-use-overlay-text'];ids.forEach(function createElementRef(name){dom[toCamelCase(name)]=document.getElementById(name);});dom.player.mozAudioChannelType='content';function $(id){return document.getElementById(id);}
var seeker=new Seeker(dom.player);var captions=new Captions(dom.player);var playing=false;var playerShowing=false;var controlShowing=false;var controlFadeTimeout=null;var selectedFileNames=[];var selectedFileNamesToBlobs={};var videodb;var currentVideo;var currentVideoBlob;var firstScanEnded=false;var THUMBNAIL_WIDTH;var THUMBNAIL_HEIGHT;var HAVE_NOTHING=0;var storageState;var currentOverlay;var dragging=false;var touchStartID=null;var sliderRect;var thumbnailList;var pendingPick;var videoHardwareReleased=false;var restoreTime=null;var isPhone;var isPortrait;var currentLayoutMode;var pendingUpdateTitleText=false;var FROMCAMERA=/DCIM\/\d{3}MZLLA\/VID_\d{4}\.3gp$/;var loadingChecker=new VideoLoadingChecker(dom.player,dom.inUseOverlay,dom.inUseOverlayTitle,dom.inUseOverlayText);var lastPlayTime=0;init();dom.player.addEventListener('play',function(){lastPlayTime=window.performance.now();});dom.player.addEventListener('mozinterruptbegin',function(){if((window.performance.now()-lastPlayTime)>50){pause();lastPlayTime=0;}});document.addEventListener('visibilitychange',function visibilityChange(){if(document.hidden){stopParsingMetadata();if(playing){pause();}}
else{if(playerShowing){if(videoHardwareReleased){restoreVideo();}
setControlsVisibility(true);}else{startParsingMetadata();}}});var acm=navigator.mozAudioChannelManager;if(acm){acm.addEventListener('headphoneschange',function onheadphoneschange(){if(!acm.headphones&&playing){setVideoPlaying(false);}});}
navigator.mozSetMessageHandler('activity',handleActivityEvents);function init(){window.performance.mark('navigationLoaded');IntlHelper.define('date-group','datetime',{month:'long',year:'numeric',});thumbnailList=new ThumbnailList(ThumbnailDateGroup,dom.thumbnails);ThumbnailItem.titleMaxLines=isPhone?2:(isPortrait?4:2);initDB();addUIEventListeners();initLayout();initThumbnailSize();function addUIEventListeners(){if(!navigator.mozHasPendingMessage('activity')){initOptionsButtons();}
document.l10n.ready.then(function(){initPlayerControls();});ForwardRewindController.init(dom.player,dom.seekForward,dom.seekBackward,seeker);dom.overlayActionButton.addEventListener('click',function(){if(pendingPick){cancelPick();}else if(currentOverlay==='empty'){launchCameraApp();}});window.performance.mark('navigationInteractive');}}
function initThumbnailSize(){if(isPhone){THUMBNAIL_WIDTH=210*window.devicePixelRatio;THUMBNAIL_HEIGHT=120*window.devicePixelRatio;}else{var shortEdge=Math.min(window.innerWidth,window.innerHeight);THUMBNAIL_WIDTH=424*window.devicePixelRatio*shortEdge/800;THUMBNAIL_HEIGHT=Math.round(THUMBNAIL_WIDTH*4/7);}}
function initLayout(){ScreenLayout.watch('portrait','(orientation: portrait)');isPhone=ScreenLayout.getCurrentLayout('tiny');isPortrait=ScreenLayout.getCurrentLayout('portrait');if(isPhone||isPortrait){dom.spinnerOverlay.classList.add('hidden');setDisabled(dom.playerView,false);}else{dom.spinnerOverlay.classList.remove('hidden');setDisabled(dom.playerView,true);}
window.addEventListener('screenlayoutchange',handleScreenLayoutChange);switchLayout(LAYOUT_MODE.list);}
function initPlayerControls(){dom.sliderWrapper.addEventListener('touchstart',handleSliderTouchStart);dom.sliderWrapper.addEventListener('touchmove',handleSliderTouchMove);dom.sliderWrapper.addEventListener('touchend',handleSliderTouchEnd);dom.player.addEventListener('timeupdate',timeUpdated);dom.player.addEventListener('seeked',updateVideoControlSlider);dom.player.addEventListener('ended',playerEnded);dom.play.addEventListener('click',handlePlayButtonClick);dom.playerHeader.addEventListener('action',handleCloseButtonClick);dom.pickerDone.addEventListener('click',postPickResult);dom.options.addEventListener('click',showOptionsView);dom.videoContainer.addEventListener('click',toggleVideoControls);dom.timeSlider.addEventListener('keypress',handleSliderKeypress);}
function initOptionsButtons(){dom.thumbnailsVideoButton.addEventListener('click',launchCameraApp);dom.thumbnailsSelectButton.addEventListener('click',showSelectView);dom.thumbnailsSelectTop.addEventListener('action',hideSelectView);dom.thumbnailsDeleteButton.addEventListener('click',deleteSelectedItems);dom.thumbnailsShareButton.addEventListener('click',shareSelectedItems);dom.infoCloseButton.addEventListener('click',hideInfoView);dom.optionsView.addEventListener('gaiamenu-cancel',hideOptionsView);dom.fullscreenButton.addEventListener('click',toggleFullscreenPlayer);addEventListeners('.single-delete-button','click',deleteCurrentVideo);addEventListeners('.single-share-button','click',shareCurrentVideo);addEventListeners('.single-info-button','click',showInfoView);}
function addEventListeners(selector,type,listener){var elements=document.body.querySelectorAll(selector);for(var i=0;i<elements.length;i++){elements[i].addEventListener(type,listener);}}
function toggleFullscreenPlayer(){if(currentLayoutMode===LAYOUT_MODE.list){switchLayout(LAYOUT_MODE.fullscreenPlayer);scheduleVideoControlsAutoHiding();}else{switchLayout(LAYOUT_MODE.list);}
VideoUtils.fitContainer(dom.videoContainer,dom.player,currentVideo.metadata.rotation||0);}
function toggleVideoControls(e){if(controlFadeTimeout){clearTimeout(controlFadeTimeout);controlFadeTimeout=null;}
if(!pendingPick){e.cancelBubble=!controlShowing;setControlsVisibility(!controlShowing);}}
function handleScreenLayoutChange(){isPortrait=ScreenLayout.getCurrentLayout('portrait');if(!isPhone){if(!isPortrait&&(!firstScanEnded||processingQueue)){dom.spinnerOverlay.classList.remove('hidden');setDisabled(dom.playerView,true);}else{dom.spinnerOverlay.classList.add('hidden');setDisabled(dom.playerView,false);}
if(currentLayoutMode===LAYOUT_MODE.list){if(isPortrait){hidePlayer(true);}else{showPlayer(currentVideo,false,false,true);}}
ThumbnailItem.titleMaxLines=isPortrait?4:2;}
if(currentLayoutMode!==LAYOUT_MODE.fullscreenPlayer){if(!thumbnailList){return;}
thumbnailList.updateAllThumbnailTitles();}else{pendingUpdateTitleText=true;}
if(dom.player.readyState!==HAVE_NOTHING){VideoUtils.fitContainer(dom.videoContainer,dom.player,currentVideo.metadata.rotation||0);}}
function switchLayout(mode){var oldMode=currentLayoutMode;if(oldMode){document.body.classList.remove(currentLayoutMode);}
currentLayoutMode=mode;document.body.classList.add(currentLayoutMode);if(oldMode===LAYOUT_MODE.fullscreenPlayer&&pendingUpdateTitleText){pendingUpdateTitleText=false;thumbnailList.updateAllThumbnailTitles();}}
function handleActivityEvents(a){var activityName=a.source.name;if(activityName==='pick'){pendingPick=a;showPickView();}}
function showInfoView(){hideOptionsView();var length=isFinite(currentVideo.metadata.duration)?MediaUtils.formatDuration(currentVideo.metadata.duration):'';var type=currentVideo.type;if(type){var index=currentVideo.type.indexOf('/');type=index>-1?currentVideo.type.slice(index+1):currentVideo.type;}
var resolution=(currentVideo.metadata.width&&currentVideo.metadata.height)?currentVideo.metadata.width+'x'+
currentVideo.metadata.height:'';return MediaUtils.getLocalizedSizeTokens(currentVideo.size).then((args)=>{var data={'info-name':{raw:currentVideo.metadata.title},'info-length':{raw:length},'info-size':{id:'fileSize',args:args},'info-type':{raw:type},'info-date':{raw:MediaUtils.formatDate(currentVideo.date)},'info-resolution':{raw:resolution}};MediaUtils.populateMediaInfo(data);setNFCSharing(false);dom.infoView.classList.remove('hidden');document.body.classList.add('info-view');});}
function hideInfoView(){setNFCSharing(true);dom.infoView.classList.add('hidden');document.body.classList.remove('info-view');}
function showSelectView(){hidePlayer(true,function(){switchLayout(LAYOUT_MODE.selection);thumbnailList.setSelectMode(true);clearSelection();});}
function hideSelectView(){clearSelection();thumbnailList.setSelectMode(false);switchLayout(LAYOUT_MODE.list);if(!isPhone&&!isPortrait&&currentVideo){showPlayer(currentVideo,false,false,true);}}
function showOptionsView(){if(playing){pause();}
dom.optionsView.removeAttribute('hidden');document.body.classList.add('options-view');}
function hideOptionsView(){dom.optionsView.setAttribute('hidden',true);document.body.classList.remove('options-view');}
function setDisabled(element,disabled){element.classList.toggle('disabled',disabled);element.setAttribute('aria-disabled',disabled);}
function setSelected(element,selected){element.classList.toggle('selected',selected);element.setAttribute('aria-selected',selected);}
function clearSelection(){Array.forEach(selectedFileNames,function(name){setSelected(thumbnailList.thumbnailMap[name].htmlNode,false);});selectedFileNames=[];selectedFileNamesToBlobs={};setDisabled(dom.thumbnailsDeleteButton,true);setDisabled(dom.thumbnailsShareButton,true);document.l10n.setAttributes(dom.thumbnailsNumberSelected,'number-selected2',{n:0});}
function updateSelection(videodata){var thumbnail=thumbnailList.thumbnailMap[videodata.name];var selected=!thumbnail.htmlNode.classList.contains('selected');setSelected(thumbnail.htmlNode,selected);var filename=videodata.name;if(selected){selectedFileNames.push(filename);videodb.getFile(filename,function(blob){selectedFileNamesToBlobs[filename]=blob;});}
else{delete selectedFileNamesToBlobs[filename];var i=selectedFileNames.indexOf(filename);if(i!==-1){selectedFileNames.splice(i,1);}}
var numSelected=selectedFileNames.length;document.l10n.setAttributes(dom.thumbnailsNumberSelected,'number-selected2',{n:numSelected});var noneSelected=numSelected===0;setDisabled(dom.thumbnailsDeleteButton,noneSelected);setDisabled(dom.thumbnailsShareButton,noneSelected);}
function launchCameraApp(){var a=new MozActivity({name:'record',data:{type:'videos'}});a.onerror=function(){if(a.error.name==='NO_PROVIDER'){document.l10n.formatValue('share-noprovider').then((msg)=>{alert(msg);});}else{console.warn('share activity error:',a.error.name);}};}
function resetCurrentVideo(){if(!currentVideo){return;}
var currentThumbnail=thumbnailList.thumbnailMap[currentVideo.name];currentThumbnail.htmlNode.classList.remove('focused');var nextThumbnail=thumbnailList.findNextThumbnail(currentVideo.name);if(nextThumbnail){currentVideo=nextThumbnail.data;nextThumbnail.htmlNode.classList.add('focused');}else{currentVideo=null;}}
function deleteSelectedItems(){if(selectedFileNames.length===0){return;}
LazyLoader.load('shared/style/confirm.css',function(){document.body.classList.add('confirm-dialog');Dialogs.confirm({messageId:'delete-n-items?',messageArgs:{n:selectedFileNames.length},cancelId:'cancel',confirmId:'delete',danger:true},function(){for(var i=0;i<selectedFileNames.length;i++){deleteFile(selectedFileNames[i]);}
clearSelection();document.body.classList.remove('confirm-dialog');},function(){document.body.classList.remove('confirm-dialog');});});}
function deleteFile(filename){if(FROMCAMERA.test(filename)){var postername=filename.replace('.3gp','.jpg');navigator.getDeviceStorage('pictures').delete(postername);}
videodb.deleteFile(filename);}
function shareSelectedItems(){var blobs=selectedFileNames.map(function(name){return selectedFileNamesToBlobs[name];});share(blobs);}
function share(blobs){if(blobs.length===0){return;}
var names=[],fullpaths=[];blobs.forEach(function(blob){var name=blob.name;fullpaths.push(name);name=name.substring(name.lastIndexOf('/')+1);names.push(name);});if(playerShowing){releaseVideo();}
var a=new MozActivity({name:'share',data:{type:'video/*',number:blobs.length,blobs:blobs,filenames:names,filepaths:fullpaths}});a.onsuccess=restoreVideo;a.onerror=function(){if(a.error.name==='NO_PROVIDER'){document.l10n.formatValue('share-noprovider').then((msg)=>{alert(msg);});}else{console.warn('share activity error:',a.error.name);}
restoreVideo();};}
function updateDialog(){if(thumbnailList.count!==0&&(!storageState||playerShowing)){showOverlay(null);}else if(storageState===MediaDB.UPGRADING){showOverlay('upgrade');}else if(storageState===MediaDB.NOCARD){showOverlay('nocard');}else if(storageState===MediaDB.UNMOUNTED){showOverlay('pluggedin');}else if(firstScanEnded&&thumbnailList.count===0&&metadataQueue.length===0){showOverlay('empty');}}
function updateLoadingSpinner(){if(processingQueue){noMoreWorkCallback=updateLoadingSpinner;}else{window.performance.mark('scanEnd');dom.spinnerOverlay.classList.add('hidden');setDisabled(dom.playerView,false);if(thumbnailList.count){currentVideo=currentVideo||thumbnailList.itemGroups[0].thumbnails[0].data;if(!isPhone&&!isPortrait){showPlayer(currentVideo,false,false,true);}}}}
function thumbnailClickHandler(videodata){if(!isPhone&&!isPortrait){if(!firstScanEnded||processingQueue){return;}}
if(currentLayoutMode===LAYOUT_MODE.list){hidePlayer(true,function(){stopParsingMetadata(function(){var fullscreen=pendingPick||isPhone||isPortrait;showPlayer(videodata,!pendingPick,fullscreen,pendingPick);});});}
else if(currentLayoutMode===LAYOUT_MODE.selection){updateSelection(videodata);}}
function showOverlay(id){LazyLoader.load('shared/style/confirm.css',function(){currentOverlay=id;if(id===null){document.body.classList.remove('overlay');dom.overlay.classList.add('hidden');return;}
var text,title;if(pendingPick||id==='empty'){dom.overlayMenu.classList.remove('hidden');dom.overlayActionButton.classList.remove('hidden');dom.overlayActionButton.setAttribute('data-l10n-id',pendingPick?'overlay-cancel-button':'overlay-camera-button');}else{dom.overlayMenu.classList.add('hidden');dom.overlayActionButton.classList.add('hidden');}
if(id==='nocard'){title='nocard2-title';text='nocard3-text';}else{title=id+'-title';text=id+'-text';}
dom.overlayTitle.setAttribute('data-l10n-id',title);dom.overlayText.setAttribute('data-l10n-id',text);dom.overlay.classList.remove('hidden');document.body.classList.add('overlay');});}
function setControlsVisibility(visible){if(isPhone||isPortrait||currentLayoutMode!==LAYOUT_MODE.list){dom.playerView.classList[visible?'remove':'add']('video-controls-hidden');controlShowing=visible;}else{controlShowing=true;}
dom.videoContainer.setAttribute('data-l10n-id',controlShowing?'hide-controls-button':'show-controls-button');if(controlShowing){updateVideoControlSlider();}}
function movePlayHead(percent){if(document.documentElement.dir==='ltr'){dom.playHead.style.left=percent;}
else{dom.playHead.style.right=percent;}}
function updateVideoControlSlider(){var percent=(dom.player.currentTime/dom.player.duration)*100;if(isNaN(percent)){return;}
percent+='%';dom.elapsedText.textContent=MediaUtils.formatDuration(dom.player.currentTime);dom.elapsedTime.style.width=percent;if(!dragging){movePlayHead(percent);}}
function setVideoPlaying(playing){if(playing){play();}else{pause();}}
function deleteCurrentVideo(){hideOptionsView();setNFCSharing(false);LazyLoader.load('shared/style/confirm.css',function(){document.body.classList.add('confirm-dialog');Dialogs.confirm({messageId:'delete-video?',cancelId:'cancel',confirmId:'delete',danger:true},function _onSuccess(){deleteFile(currentVideo.name);if(!isPhone&&!isPortrait){if(currentVideo){showPlayer(currentVideo,false,true,true);}}else{hidePlayer(false);}
document.body.classList.remove('confirm-dialog');},function _onError(){setNFCSharing(true);document.body.classList.remove('confirm-dialog');});});}
function handlePlayButtonClick(){setVideoPlaying(dom.player.paused);}
function handleCloseButtonClick(){if(isPhone||isPortrait){hidePlayer(true);}else{toggleFullscreenPlayer();}}
function postPickResult(){pendingPick.postResult({type:currentVideoBlob.type,blob:currentVideoBlob});cleanupPick();}
function shareCurrentVideo(){hideOptionsView();videodb.getFile(currentVideo.name,function(blob){share([blob]);});}
function handleSliderTouchStart(event){if(null!=touchStartID){return;}
touchStartID=event.changedTouches[0].identifier;dragging=true;sliderRect=dom.sliderWrapper.getBoundingClientRect();if(dom.player.duration===Infinity){return;}
handleSliderTouchMove(event);}
function setVideoUrl(player,video,callback){function handleLoadedMetadata(){dom.player.onloadedmetadata=null;callback();}
function loadVideo(url){loadingChecker.ensureVideoLoads(handleLoadedMetadata);player.src=url;}
captions.remove();if('name'in video){videodb.getFile(video.name,function(file){captions.findAndDisplay(file.name);var url=URL.createObjectURL(file);loadVideo(url);if(pendingPick){currentVideoBlob=file;}});}else if('url'in video){loadVideo(video.url);}}
function scheduleVideoControlsAutoHiding(){controlFadeTimeout=setTimeout(function(){setControlsVisibility(false);},250);}
function setNFCSharing(enable){if(!window.navigator.mozNfc){return;}
if(enable){window.navigator.mozNfc.onpeerready=function(event){videodb.getFile(currentVideo.name,function(file){event.peer.sendFile(file);});};}else{window.navigator.mozNfc.onpeerready=null;}}
function showPlayer(video,autoPlay,enterFullscreen,keepControls){if(currentVideo){var old=thumbnailList.thumbnailMap[currentVideo.name];old.htmlNode.classList.remove('focused');}
currentVideo=video;var thumbnail=thumbnailList.thumbnailMap[currentVideo.name];thumbnail.htmlNode.classList.add('focused');updateDialog();dom.player.preload='metadata';function doneSeeking(){dom.player.onseeked=null;setControlsVisibility(true);if(!keepControls){scheduleVideoControlsAutoHiding();}
if(autoPlay){play();}else{pause();}
dom.player.hidden=false;}
dom.player.hidden=true;setVideoUrl(dom.player,currentVideo,function(){if(enterFullscreen){switchLayout(LAYOUT_MODE.fullscreenPlayer);}
var formattedDuration=MediaUtils.formatDuration(dom.player.duration);dom.durationText.textContent=formattedDuration;timeUpdated();setButtonPaused(false);playerShowing=true;var rotation;if('metadata'in currentVideo){if(currentVideo.metadata.currentTime===dom.player.duration){currentVideo.metadata.currentTime=0;}
dom.videoTitle.textContent=currentVideo.metadata.title;dom.player.currentTime=currentVideo.metadata.currentTime||0;rotation=currentVideo.metadata.rotation;}else{dom.videoTitle.textContent=currentVideo.title||'';dom.player.currentTime=0;rotation=0;}
document.l10n.setAttributes(dom.timeSlider,'seek-bar',{duration:formattedDuration});dom.timeSlider.setAttribute('aria-valuemin',0);dom.timeSlider.setAttribute('aria-valuemax',dom.player.duration);dom.timeSlider.setAttribute('aria-valuenow',dom.player.currentTime);dom.timeSlider.setAttribute('aria-valuetext',MediaUtils.formatDuration(dom.player.currentTime));VideoUtils.fitContainer(dom.videoContainer,dom.player,rotation||0);if(dom.player.seeking){dom.player.onseeked=doneSeeking;}else{doneSeeking();}
setNFCSharing(true);});}
function hidePlayer(updateVideoMetadata,callback){if(!playerShowing){if(callback){callback();}
return;}
dom.player.pause();setNFCSharing(false);function completeHidingPlayer(){switchLayout(LAYOUT_MODE.list);setButtonPaused(false);playerShowing=false;updateDialog();captions.remove();dom.player.removeAttribute('src');dom.player.load();startParsingMetadata();if(callback){callback();}}
if(!('metadata'in currentVideo)||!updateVideoMetadata||pendingPick){completeHidingPlayer();return;}
var video=currentVideo;var thumbnail=thumbnailList.thumbnailMap[video.name];if(dom.player.currentTime===0){video.metadata.bookmark=null;updateMetadata();}
else{captureFrame(dom.player,video.metadata,function(bookmark){video.metadata.bookmark=bookmark;updateMetadata();});}
function updateMetadata(){thumbnail.updatePoster(video.metadata.bookmark||video.metadata.poster);if(!video.metadata.watched){video.metadata.watched=true;thumbnail.setWatched(true);}
video.metadata.currentTime=dom.player.currentTime;videodb.updateMetadata(video.name,video.metadata);completeHidingPlayer();}}
function playerEnded(){if(dragging){return;}
if(playing){dom.player.currentTime=0;pause();}}
function setButtonPaused(paused){dom.play.classList.toggle('paused',paused);dom.play.setAttribute('data-l10n-id',paused?'play-button':'pause-button');}
function play(){loadingChecker.ensureVideoPlays();setButtonPaused(false);VideoStats.start(dom.player);dom.player.play();playing=true;}
function pause(){loadingChecker.cancelEnsureVideoPlays();setButtonPaused(true);if(dragging){dragging=false;dom.playHead.classList.remove('active');}
dom.player.pause();playing=false;VideoStats.stop();VideoStats.dump();}
function timeUpdated(){if(controlShowing){if(dom.player.duration===Infinity||dom.player.duration===0){return;}
updateVideoControlSlider();}
dom.timeSlider.setAttribute('aria-valuenow',dom.player.currentTime);dom.timeSlider.setAttribute('aria-valuetext',MediaUtils.formatDuration(dom.player.currentTime));}
function handleSliderTouchEnd(event){if(!event.changedTouches.identifiedTouch(touchStartID)){return;}
touchStartID=null;if(!dragging){return;}
dragging=false;dom.playHead.classList.remove('active');updateVideoControlSlider();if(dom.player.currentTime===dom.player.duration){pause();}}
function handleSliderTouchMove(event){if(!dragging){return;}
var touch=event.changedTouches.identifiedTouch(touchStartID);if(!touch){return;}
function getTouchPos(){return(document.documentElement.dir==='ltr')?(touch.clientX-sliderRect.left):(sliderRect.right-touch.clientX);}
var touchPos=getTouchPos();var pos=touchPos/sliderRect.width;pos=Math.max(pos,0);pos=Math.min(pos,1);var percent=pos*100+'%';dom.playHead.classList.add('active');movePlayHead(percent);dom.elapsedTime.style.width=percent;seeker.seekTo(dom.player.duration*pos);}
function handleSliderKeypress(event){var step=Math.max(dom.player.duration/20,2);if(event.keyCode===event.DOM_VK_DOWN){seeker.seekTo(dom.player.currentTime-step);}else if(event.keyCode===event.DOM_VK_UP){seeker.seekTo(dom.player.currentTime+step);}}
function toCamelCase(str){return str.replace(/\-(.)/g,function replacer(str,p1){return p1.toUpperCase();});}
function showPickView(){thumbnailList.setPickMode(true);document.body.classList.add('pick-activity');dom.pickerHeader.addEventListener('action',cancelPick);if(!isPhone&&!isPortrait){thumbnailList.updateAllThumbnailTitles();}}
function cancelPick(){pendingPick.postError('pick cancelled');cleanupPick();}
function cleanupPick(){pendingPick=null;currentVideoBlob=null;hidePlayer(false);}
function showThrobber(){dom.throbber.classList.remove('hidden');dom.throbber.classList.add('throb');}
function hideThrobber(){dom.throbber.classList.remove('throb');setTimeout(function(){dom.throbber.classList.add('hidden');},100);}
function releaseVideo(){if(videoHardwareReleased){return;}
videoHardwareReleased=true;if(dom.player.readyState>0){restoreTime=dom.player.currentTime;}
else{restoreTime=0;}
captions.remove();dom.player.removeAttribute('src');dom.player.load();}
function restoreVideo(){if(!videoHardwareReleased){return;}
videoHardwareReleased=false;setVideoUrl(dom.player,currentVideo,function(){VideoUtils.fitContainer(dom.videoContainer,dom.player,currentVideo.metadata.rotation||0);dom.player.currentTime=restoreTime;});}
window.addEventListener('storage',function(e){if(e.key==='view-activity-wants-to-use-hardware'&&e.newValue&&!document.hidden&&playerShowing&&!videoHardwareReleased){console.log('The video app view activity needs to play a video.');console.log('Pausing the video and returning to the thumbnails.');console.log('See bug 1088456.');handleCloseButtonClick();}});

View file

@ -0,0 +1,4 @@
;(function(exports){'use strict';exports.VideoLoadingChecker=VideoLoadingChecker;function VideoLoadingChecker(player,overlay,overlayTitle,overlayText){this.player=player;this.overlay=overlay;this.overlayTitle=overlayTitle;this.overlayText=overlayText;}
VideoLoadingChecker.prototype={ensureVideoLoads:function vpc_ensureVideoLoads(callback){var self=this;var videoLoadedTimeoutId=setTimeout(showOverlay.bind(null,this),1500);this.player.addEventListener('loadedmetadata',handleLoadedMetadata);function handleLoadedMetadata(){clearTimeout(videoLoadedTimeoutId);videoLoadedTimeoutId=null;self.player.removeEventListener('loadedmetadata',handleLoadedMetadata);hideOverlay(self);if(callback){callback();}}},ensureVideoPlays:function(){this.playingTimeout=setTimeout(showOverlay.bind(null,this),1500);this.playingListener=this.cancelEnsureVideoPlays.bind(this);this.player.addEventListener('timeupdate',this.playingListener);},cancelEnsureVideoPlays:function(){hideOverlay(this);if(this.playingTimeout){clearTimeout(this.playingTimeout);this.playingTimeout=null;}
if(this.playingListener){this.player.removeEventListener('timeupdate',this.playingListener);this.playingListener=null;}}};function showOverlay(checker){checker.overlayTitle.setAttribute('data-l10n-id','video-hardware-in-use-title');checker.overlayText.setAttribute('data-l10n-id','video-hardware-in-use-text');checker.overlay.classList.remove('hidden');document.body.classList.add('in-use-overlay');}
function hideOverlay(checker){document.body.classList.remove('in-use-overlay');checker.overlay.classList.add('hidden');}}(window));

View file

@ -0,0 +1,13 @@
;'use strict';var VideoUtils={fitContainer:function(container,player,videoRotation){var containerWidth=container.clientWidth;var containerHeight=container.clientHeight;if(!player.videoWidth||!player.videoHeight){return;}
var width,height;var rotation=videoRotation||0;switch(rotation){case 0:case 180:width=player.videoWidth;height=player.videoHeight;break;case 90:case 270:width=player.videoHeight;height=player.videoWidth;}
var xscale=containerWidth/width;var yscale=containerHeight/height;var scale=Math.min(xscale,yscale);width*=scale;height*=scale;var left=((containerWidth-width)/2);var top=((containerHeight-height)/2);var transform;switch(rotation){case 0:transform='translate('+left.toFixed(4)+'px,'+
top.toFixed(4)+'px)';break;case 90:transform='translate('+(left+width).toFixed(4)+'px,'+
top.toFixed(4)+'px) '+'rotate(90deg)';break;case 180:transform='translate('+(left+width).toFixed(4)+'px,'+
(top+height).toFixed(4)+'px) '+'rotate(180deg)';break;case 270:transform='translate('+left.toFixed(4)+'px,'+
(top+height).toFixed(4)+'px) '+'rotate(270deg)';break;}
transform+=' scale('+scale.toFixed(4)+')';player.style.transform=transform;},getTruncated:function(oldName,options){var maxLine=options.maxLine||2;var node=options.node;var ellipsisCharacter=options.ellipsisCharacter||'...';if(node===null){return oldName;}
var realVisibility=node.style.visibility;var realWordBreak=node.style.wordBreak;node.style.visibility='hidden';node.style.wordBreak='break-all';node.textContent='.';var baseHeight=node.clientHeight;var computedStyle=window.getComputedStyle(node,null);var paddingTop=parseInt(computedStyle.getPropertyValue('padding-top'),10);var paddingBottom=parseInt(computedStyle.getPropertyValue('padding-bottom'),10);var padding=paddingTop+paddingBottom;var lineHeight=baseHeight-padding;node.textContent=oldName;var originalTitleHeight=node.clientHeight-padding;var linesForOriginalTitle=(originalTitleHeight/lineHeight);if(linesForOriginalTitle<=maxLine){node.style.visibility=realVisibility;node.style.wordBreak=realWordBreak;return oldName;}
function titleTooLong(pos){node.textContent=oldName.slice(0,pos)+ellipsisCharacter;var tempTitleHeight=node.clientHeight-padding;return(tempTitleHeight/lineHeight)>maxLine;}
var low=0;var high=oldName.length-1;var mid;var ellipsisIndex=0;do{mid=Math.floor((low+high)/2);if(titleTooLong(mid)){high=mid-1;}
else{ellipsisIndex=mid;low=mid+1;}}
while(low<=high);var newName=oldName.slice(0,ellipsisIndex)+ellipsisCharacter;node.style.visibility=realVisibility;node.style.wordBreak=realWordBreak;return newName;}};

View file

@ -0,0 +1,595 @@
'use strict';
/* global
getStorageIfAvailable,
getVideoRotation,
VideoUtils,
ForwardRewindController,
MimeMapper,
getUnusedFilename,
MediaUtils,
VideoLoadingChecker,
MediaError,
Seeker
*/
/*
* This is a stripped down version of video.js that handles the view activity
* and displays streaming videos specified by url.
*
* Unfortunately, there is a fair bit of code duplication between this
* file and video.js, but we are too close to the v1 deadline to refactor
* the shared code into a single shared file. If the video player UI changes
* those changes will have to be made in both video.js and view.js
*/
(function init() {
var dom = {}; // document elements
var seeker;
var playing = false;
var controlShowing = false;
var controlFadeTimeout = null;
var dragging = false;
var storage; // A device storage object used by the save button
var saved = false; // Did we save it?
var videoRotation = 0;
var activity; // The activity object we're handling
var data; // The data sent by the initiating app
var blob; // The blob we'll be displaying and maybe saving
var url; // URL string representing the blob object
var title; // What we call the video in the titlebar and banner
// touch start id is the identifier of touch event. we only need to process
// events related to this id.
var touchStartID = null;
var sliderRect;
window.pause = pause;
//
// Bug 1088456: when the view activity is launched by the bluetooth transfer
// app (when the user taps on a downloaded file in the notification tray)
// this code starts running while the regular video app is still running as
// the foreground app. Since the video app does not get sent to the
// background in this case, the currently playing video (if there is one) is
// not paused. And so, in the case of videos that require decoder hardware,
// the view activity cannot play the video. For this workaround, we have set
// a localStorage property here. The video.js file should receive an event
// when we do that and will use that as a signal to unload its video. We use
// Date.now() as the value of the property so we get a different value and
// generate an event each time we run.
//
localStorage.setItem('view-activity-wants-to-use-hardware', Date.now());
// Register a handler to receive the Activity object
navigator.mozSetMessageHandler('activity', handleViewActivity);
function handleViewActivity(request) {
activity = request;
data = activity.source.data;
blob = data.blob;
url = data.url;
title = data.title || '';
// Set up the UI, if it is not already set up
if (!dom.player) {
initUI();
}
// Hide save button
dom.save.hidden = true;
// If blob exists, video should be launched by open activity
if (blob) {
// The title we display for this video may be explicitly specified,
// or it might be the specified filename to save to or it might be
// the filename of the blob.
title = data.title || baseName(data.filename) || baseName(blob.name);
url = URL.createObjectURL(blob);
// If the app that initiated this activity wants us to allow the
// user to save this blob as a file, and if device storage is available
// and if there is enough free space, then display a save button.
if (data.allowSave && data.filename && checkFilename()) {
getStorageIfAvailable('videos', blob.size, function(ds) {
dom.save.hidden = false;
// HACK: Cause gaia-header to re-run font-fit logic
// now that the 'save' button is visible.
dom.videoTitle.textContent = dom.videoTitle.textContent;
storage = ds;
});
}
// to hide player because it shows in the wrong rotation.
dom.player.classList.add('hidden');
// video rotation is not parsed, parse it.
getVideoRotation(blob, function(rotation) {
// when error found, fallback to 0
if (typeof rotation === 'string') {
console.error('get video rotation error: ' + rotation);
videoRotation = 0;
} else {
videoRotation = rotation;
}
// show player when player size and rotation are correct.
dom.player.classList.remove('hidden');
// start to play the video that showPlayer also calls fitContainer.
showPlayer(url, title);
});
} else {
// In the url case, we don't need to calculate the rotation.
showPlayer(url, title);
}
}
// Terminate video playback when visibility is changed.
window.addEventListener('visibilitychange',
function onVisibilityChanged() {
if (document.hidden) {
done();
}
});
// We get headphoneschange event when the headphones is plugged or unplugged
var acm = navigator.mozAudioChannelManager;
if (acm) {
acm.addEventListener('headphoneschange', function onheadphoneschange() {
if (!acm.headphones && playing) {
setVideoPlaying(false);
}
});
}
function setVideoPlaying(playing) {
if (playing) {
play();
} else {
pause();
}
}
function initUI() {
// Fullscreen mode and inline activities don't seem to play well together
// so we'll play the video without going into fullscreen mode.
// Get all the elements we use by their id
var ids = ['player', 'player-view',
'player-header', 'play', 'playHead', 'video-container',
'elapsedTime', 'video-title', 'duration-text', 'elapsed-text',
'slider-wrapper', 'spinner-overlay', 'timeSlider',
'save', 'banner', 'message', 'seek-forward',
'seek-backward', 'videoControlBar', 'in-use-overlay',
'in-use-overlay-title', 'in-use-overlay-text'];
ids.forEach(function createElementRef(name) {
dom[toCamelCase(name)] = document.getElementById(name);
});
function toCamelCase(str) {
return str.replace(/\-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
}
seeker = new Seeker(dom.player);
dom.player.mozAudioChannelType = 'content';
// handling the dragging of slider
dom.sliderWrapper.addEventListener('touchstart', handleSliderTouchStart);
dom.sliderWrapper.addEventListener('touchmove', handleSliderTouchMove);
dom.sliderWrapper.addEventListener('touchend', handleSliderTouchEnd);
// Rescale when window size changes. This should get called when
// orientation changes.
window.addEventListener('resize', function() {
if (dom.player.readyState !== dom.player.HAVE_NOTHING) {
VideoUtils.fitContainer(dom.videoContainer, dom.player,
videoRotation || 0);
}
});
dom.player.addEventListener('timeupdate', timeUpdated);
dom.player.addEventListener('seeked', updateSlider);
// showing + hiding the loading spinner
dom.player.addEventListener('waiting', showSpinner);
dom.player.addEventListener('playing', hideSpinner);
dom.player.addEventListener('play', hideSpinner);
dom.player.addEventListener('pause', hideSpinner);
dom.player.addEventListener('ended', playerEnded);
dom.player.addEventListener('canplaythrough', hideSpinner);
// option buttons
dom.play.addEventListener('click', handlePlayButtonClick);
dom.playerHeader.addEventListener('action', done);
dom.save.addEventListener('click', save);
// show/hide controls
dom.videoContainer.addEventListener('click', toggleVideoControls);
// handle slider keypress, emitted by the screen reader
dom.timeSlider.addEventListener('keypress', handleSliderKeypress);
ForwardRewindController.init(dom.player, dom.seekForward, dom.seekBackward,
seeker);
}
function checkFilename() {
var dotIdx = data.filename.lastIndexOf('.');
if (dotIdx > -1) {
var ext = data.filename.substr(dotIdx + 1);
return MimeMapper.guessTypeFromExtension(ext) === blob.type;
} else {
return false;
}
}
function setControlsVisibility(visible) {
dom.playerView.classList[visible ? 'remove' : 'add'](
'video-controls-hidden');
controlShowing = visible;
// Set the proper accessibility label for the video container based on
// controls showing.
dom.videoContainer.setAttribute('data-l10n-id', controlShowing ?
'hide-controls-button' : 'show-controls-button');
if (visible) {
// update elapsed time and slider while showing.
updateSlider();
}
}
function handlePlayButtonClick() {
setVideoPlaying(dom.player.paused);
}
function toggleVideoControls(e) {
// When we change the visibility state of video controls, we need to check
// the timeout of auto hiding.
if (controlFadeTimeout) {
clearTimeout(controlFadeTimeout);
controlFadeTimeout = null;
}
// We cannot change the visibility state of video contorls when we are in
// picking mode.
e.cancelBubble = !controlShowing;
setControlsVisibility(!controlShowing);
}
function handleSliderTouchStart(event) {
// If we have a touch start event, we don't need others.
if (null != touchStartID) {
return;
}
touchStartID = event.changedTouches[0].identifier;
dragging = true;
// calculate the slider wrapper size for slider dragging.
sliderRect = dom.sliderWrapper.getBoundingClientRect();
// We can't do anything if we don't know our duration
if (dom.player.duration === Infinity) {
return;
}
handleSliderTouchMove(event);
}
function done() {
pause();
// Release any video resources
dom.player.removeAttribute('src');
dom.player.load();
// End the activity
activity.postResult({saved: saved});
// Undo the bug 1088456 workaround hack.
localStorage.removeItem('view-activity-wants-to-use-hardware');
}
function save() {
// Hide the menu that holds the save button: we can only save once
dom.save.hidden = true;
// XXX work around bug 870619
dom.videoTitle.textContent = dom.videoTitle.textContent;
getUnusedFilename(storage, data.filename, function(filename) {
var savereq = storage.addNamed(blob, filename);
savereq.onsuccess = function() {
// Remember that it has been saved so we can pass this back
// to the invoking app
saved = filename;
// And tell the user
showBanner({
id: 'saved',
args: { title: title }
});
};
savereq.onerror = function(e) {
// XXX we don't report this to the user because it is hard to
// localize.
console.error('Error saving', filename, e);
};
});
}
// show video player
function showPlayer(url, title) {
function handleLoadedMetadata() {
var formattedDuration = MediaUtils.formatDuration(dom.player.duration);
dom.durationText.textContent = formattedDuration;
timeUpdated();
setButtonPaused(false);
VideoUtils.fitContainer(dom.videoContainer, dom.player,
videoRotation || 0);
dom.player.currentTime = 0;
// Show the controls briefly then fade out
controlFadeTimeout = setTimeout(function() {
setControlsVisibility(false);
}, 2000);
document.l10n.setAttributes(dom.timeSlider, 'seek-bar',
{ duration: formattedDuration });
dom.timeSlider.setAttribute('aria-valuemin', 0);
dom.timeSlider.setAttribute('aria-valuemax', dom.player.duration);
dom.timeSlider.setAttribute('aria-valuenow', dom.player.currentTime);
dom.timeSlider.setAttribute('aria-valuetext',
MediaUtils.formatDuration(dom.player.currentTime));
play();
}
dom.videoTitle.textContent = title || '';
setControlsVisibility(true);
var loadingChecker =
new VideoLoadingChecker(dom.player, dom.inUseOverlay,
dom.inUseOverlayTitle,
dom.inUseOverlayText);
loadingChecker.ensureVideoLoads(handleLoadedMetadata);
dom.player.src = url;
dom.player.onloadeddata = function(evt) { URL.revokeObjectURL(url); };
dom.player.onerror = function(evt) {
var errorid = '';
switch (evt.target.error.code) {
case MediaError.MEDIA_ERR_ABORTED:
// This aborted error should be triggered by the user
// so we don't have to show any error messages
return;
case MediaError.MEDIA_ERR_NETWORK:
errorid = 'error-network';
break;
case MediaError.MEDIA_ERR_DECODE:
case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
// If users tap some video link in an offline page
// the error code will be MEDIA_ERR_SRC_NOT_SUPPORTED
// we also prompt the unsupported error message for it
errorid = 'error-unsupported';
break;
// Is it possible to be unknown errors?
default:
errorid = 'error-unknown';
break;
}
handleError(errorid);
};
}
function handleError(l10nId) {
document.l10n.formatValue(l10nId).then((msg) => {
alert(msg);
done();
});
}
function play() {
// Switch the button icon
setButtonPaused(false);
// Start playing
dom.player.play();
playing = true;
}
function pause() {
// Switch the button icon
setButtonPaused(true);
// Stop playing the video
dom.player.pause();
playing = false;
}
// Update the progress bar and play head as the video plays
function timeUpdated() {
if (controlShowing) {
// We can't update a progress bar if we don't know how long
// the video is. It is kind of a bug that the <video> element
// can't figure this out for ogv videos.
if (dom.player.duration === Infinity || dom.player.duration === 0) {
return;
}
updateSlider();
}
dom.timeSlider.setAttribute('aria-valuenow', dom.player.currentTime);
dom.timeSlider.setAttribute('aria-valuetext',
MediaUtils.formatDuration(dom.player.currentTime));
}
function playerEnded() {
if (dragging) {
return;
}
// If we are still playing when this 'ended' event arrives, then the
// user played the video all the way to the end, and we skip to the
// beginning and pause so it is easy for the user to restart. If we
// reach the end because the user fast forwarded or dragged the slider
// to the end, then we will have paused the video before we get this
// event and in that case we will remain paused at the end of the video.
if (playing) {
dom.player.currentTime = 0;
pause();
}
}
function setButtonPaused(paused) {
dom.play.classList.toggle('paused', paused);
dom.play.setAttribute('data-l10n-id',
paused ? 'play-button' : 'pause-button');
}
function movePlayHead(percent) {
if (document.documentElement.dir === 'ltr') {
dom.playHead.style.left = percent;
}
else {
dom.playHead.style.right = percent;
}
}
function updateSlider() {
// We update the slider when we get a 'seeked' event.
// Don't do updates while we're seeking because the position we fastSeek()
// to probably isn't exactly where we requested and we don't want jerky
// updates
if (dom.player.seeking) {
return;
}
var percent = (dom.player.currentTime / dom.player.duration) * 100;
if (isNaN(percent)) { // this happens when we end the activity
return;
}
percent += '%';
dom.elapsedText.textContent = MediaUtils.formatDuration(
dom.player.currentTime);
dom.elapsedTime.style.width = percent;
// Don't move the play head if the user is dragging it.
if (!dragging) {
movePlayHead(percent);
}
}
function handleSliderTouchEnd(event) {
// We don't care the event not related to touchStartID
if (!event.changedTouches.identifiedTouch(touchStartID)) {
return;
}
touchStartID = null;
if (!dragging) {
// We don't need to do anything without dragging.
return;
}
dragging = false;
dom.playHead.classList.remove('active');
// We didn't move the play head while the user's finger was on it
// so now it may be in the wrong place.
updateSlider();
if (dom.player.currentTime === dom.player.duration) {
pause();
}
}
function handleSliderTouchMove(event) {
if (!dragging) {
return;
}
var touch = event.changedTouches.identifiedTouch(touchStartID);
// We don't care the event not related to touchStartID
if (!touch) {
return;
}
function getTouchPos() {
return (document.documentElement.dir === 'ltr') ?
(touch.clientX - sliderRect.left) :
(sliderRect.right - touch.clientX);
}
var touchPos = getTouchPos();
var pos = touchPos / sliderRect.width;
pos = Math.max(pos, 0);
pos = Math.min(pos, 1);
// Update the slider to match the position of the user's finger.
// Note, however, that we don't update the displayed time until
// we actually get a 'seeked' event.
var percent = pos * 100 + '%';
dom.playHead.classList.add('active');
movePlayHead(percent);
dom.elapsedTime.style.width = percent;
seeker.seekTo(dom.player.duration * pos);
}
function handleSliderKeypress(event) {
// The standard accessible control for sliders is arrow up/down keys.
// Our screenreader synthesizes those events on swipe up/down gestures.
// Currently, we only allow screen reader users to adjust sliders with a
// constant step size (there is no page up/down equivalent). In the case
// of videos, we make sure that the maximum amount of steps for the entire
// duration is 20, or 2 second increments if the duration is less then 40
// seconds.
var step = Math.max(dom.player.duration / 20, 2);
if (event.keyCode === event.DOM_VK_DOWN) {
seeker.seekTo(dom.player.currentTime - step);
} else if (event.keyCode === event.DOM_VK_UP) {
seeker.seekTo(dom.player.currentTime + step);
}
}
function showBanner(l10n) {
document.l10n.setAttributes(
dom.message,
l10n.id,
l10n.args);
dom.banner.hidden = false;
setTimeout(function() {
dom.banner.hidden = true;
}, 3000);
}
// Strip directories and just return the base filename
function baseName(filename) {
if (!filename) {
return '';
}
return filename.substring(filename.lastIndexOf('/') + 1);
}
function setDisabled(element, disabled) {
element.classList.toggle('disabled', disabled);
// Set ARIA disabled attribute to maintain semantic meaning for the
// assistive technologies like screen reader.
element.setAttribute('aria-disabled', disabled);
}
function showSpinner() {
if (!blob) {
dom.spinnerOverlay.classList.remove('hidden');
setDisabled(dom.playerView, true);
}
}
function hideSpinner() {
dom.spinnerOverlay.classList.add('hidden');
setDisabled(dom.playerView, false);
}
})();