Catching play event in video on mobile safari - iphone

I have an iOS app that contains a lot of local web content. Some of that content is video/audio. I have a click event attached to the video and audio tags that fires an analytics url that I'm going to catch in the UIWebView. The problem is that click event doesn't register. I'm assuming this is because iOS replaces the video with its own special movie player. How do I catch the play event so I can do something with it. Here's my jQuery code for the click event.
function videoClick() {
$("video").click(function () {
alert("here");
//document.location = "ignoretap:///";
var videoTitle = $(this).attr("data-mediatitle");
var videoSrc = $(this).children("source").attr("src");
if (videoTitle != null && videoTitle.length > 0) {
document.location = "analytics:///" + "page" + videoTitle;
}
else {
document.location = "analytics:///" + "page" + videoSrc;
}
});
}
Here's my html for the video
<video width="100%" controls="controls" data-mediatitle="testing">
<source src="StoryOfIyal.mp4" type="video/mp4" poster="IyalVideo.htm-iyalvideoscreenshot_lg.jpg"></source>
Your Browser does not support the video tag
</video>

Figured it out. You have to bind to the "play" event in jQuery, not the click event. Here's the updated function.
function videoClick() {
$("video").bind('play', function () {
alert("here");
//document.location = "ignoretap:///";
var videoTitle = $(this).attr("data-mediatitle");
var videoSrc = $(this).children("source").attr("src");
if (videoTitle != null && videoTitle.length > 0) {
document.location = "analytics:///" + "page" + videoTitle;
}
else {
document.location = "analytics:///" + "page" + videoSrc;
}
});
}

Related

Kaltura video player with a working VAST pre and postroll ad and custom video URLs, but not for DoubleClick

Did anyone figure out how to embed the kaltura video player with custom video urls?
It is supposed to look something like this:
<script>
var $ = jQuery;
</script>
<!-- Substitute {partner_id} for your Kaltura partner id, {uiconf_id} for uiconf player id -->
<script src="//OUR_LOCAL_SERVER/p/101/sp/10100/embedIframeJs/uiconf_id/23448200/partner_id/101"></script>
<div class="kaltura-player-wrap">
<!-- inner pusher div defines aspect ratio: in this case 16:9 ~ 56.25% -->
<div id="dummy" style="margin-top: 56.25%;"></div>
<!-- the player embed target, set to take up available absolute space -->
<div id="videoPlayer" itemprop="video" intemscope itemtype="http://schema.org/VideoObject"></div>
</div>
<script>
(function($) {
var enableAds = true;
var enablePreroll = true;
var enableMidroll = false; //not implemented yet since kaltura doesn't really support it
var enablePostroll = false;
var adlimit = 2;
dbug = true;
var flashvars = {};
var adlock = 0;
var faillock = 0;
var adtimeout = 20000;
var playlist = sfp.nextVideo !== undefined;
var mediaProxy = {
'entry': {
"thumbnailUrl": sfp.thumbnail,
"name": sfp.title,
"description": "The+Mediterranean+diet+has+long+been+promoted+as+a+heart+healthy+way+of+eating.+But%2C+for+women%2C+ther",
"duration": sfp.duration
},
'sources': [{
"src": "http://v1.SOME_SITE.com/822ece3c-52f0-4daa-b2da-7ebf1c0ba354.ogg",
"type": "video/ogg;"
}, {
"src": "http://v1.SOME_SITE.com/822ece3c-52f0-4daa-b2da-7ebf1c0ba354.webm",
"width": "624",
"height": "352",
"bandwidth": "740352",
"type": "video/webm; codecs='vp8, vorbis'",
}, {
"src": "http://v1.SOME_SITE.com/822ece3c-52f0-4daa-b2da-7ebf1c0ba354.mp4",
"width": "640",
"height": "360",
"bandwidth": "1101824",
"type": "video/mp4; codecs='avc1.42E01E, mp4a.40.2'",
}]
}
/*
Append pre or post roll ad parameters to the flashvars.vast object
obj: has to be the flashvars.vast object
uri: the ad tag url, parsed from the XML response
*/
function appendAdsVAST(obj,uri){
if(enablePreroll){
var temp = {
"prerollUrl" : uri,
"prerollUrlJs" : uri, //enable support for mobile ads
"numPreroll" : "1",
"prerollStartWith" : "1",
"prerollInterval" : "1",
"preSequence" : "1",
}
}else if(enablePostroll){
var temp = {
"postrollUrl" : uri,
"postrollUrlJs" : uri, //enable support for mobile ads
"numPostroll" : "1",
"postrollStartWith" : "1",
"postrollInterval" : "1",
"postSequence" : "1",
}
}
for(var val in temp)
obj[val] = temp[val];
return obj;
}
/*
To be reworked
uri-link to the ad to be passed into optimatic
type - optimatic
yumenetworks
bnmla
doubleclick
*/
function buildVideoPlayer(uri, type) {
//overload to default ad type - doubleclick
if(type==null || type===undefined || type==false)
type = "doubleclick";
if (type=="bnmla") {
flashvars = {
"vast": {
"plugin" : true,
"position" : "before",
"timeout" : "30",
"relativeTo" : "PlayerHolder",
},
"skipBtn": {
"skipOffset" : "5",
"label" : "Skip Ad"
},
"adsOnReplay" : true,
"inlineScript" : false,
"ForceFlashOnDesktopSafari" : false,
}
flashvars.vast = appendAdsVAST(flashvars.vast,uri);
flashvars.noticeMessage = {};
} else if (type=="yumenetworks" || type=="optimatic") {
/*
VAST DoubleClick Ad and Companion
Link: http://player.kaltura.com/docs/kvast
*/
flashvars = {
"vast": {
"plugin" : true,
"position" : "before",
"timeout" : "30",
"relativeTo" : "PlayerHolder",
},
"skipBtn": {
"skipOffset" : "5",
"label" : "Skip Ad"
},
"adsOnReplay" : true,
"inlineScript" : false,
"ForceFlashOnDesktopSafari" : false,
}
flashvars.vast = appendAdsVAST(flashvars.vast,uri);
// mobile already defines time remaining until ad finishes, so no need to specify it again
// if(!kWidget.isMobileDevice())
// flashvars.noticeMessage = {
// "plugin" : true,
// "position" : "before",
// "text" : "Time remaining {sequenceProxy.timeRemaining|timeFormat}"
// }
// disable any message overlays on the ad
flashvars.noticeMessage = {};
} else if(false){ //disbale doubleclick for now
/*
DoubleClick for the DFP plugin
http://player.kaltura.com/docs/DoubleClick
*/
flashvars = {
/*
* Doubleclick not working with mediaproxy, use generic vast instead
*/
"doubleClick": {
"plugin": true,
"path": "http://cdnbakmi.kaltura.com/content/uiconf/ps/veria/kdp3.9.1/plugins/doubleclickPlugin.swf",
"adTagUrl": uri,
"leadWithFlash": true,
"disableCompanionAds": true,
},
// "vast": {
// "prerollUrl": uri,
// "numPreroll": "1",
// "prerollStartWith": "1",
// "prerollInterval": "1",
// "postSequence": "1",
// "timeout": "4",
// "storeSession": false,
// },
}
}else{
/*
Generic VAST for other providers
*/
flashvars = {
"vast": {
"plugin" : true,
"position" : "before",
"timeout" : "30",
"relativeTo" : "PlayerHolder",
},
"skipBtn": {
"skipOffset" : "5",
"label" : "Skip Ad"
},
"adsOnReplay" : true,
"inlineScript" : false,
"ForceFlashOnDesktopSafari" : false,
}
flashvars.vast = appendAdsVAST(flashvars.vast,uri);
}
/*
For now diable ads on mobile, unless the type is optimatic, which does work now
Kaltura's Tremor, Adap.tv, YuMe, and FreeWheel plugins are not currently HTML5-compatible.
*/
if (kWidget.isMobileDevice() && type!="optimatic"){
flashvars = {};
dbug && console.log("Ads disabled on mobile, unless it's optimatic. Disabling ads");
}
/*
Ad response is empty, dont load any ads
*/
if(uri==""){
flashvars = {};
dbug && console.log("Ads link empty. Disabling ads");
}
/*
Erase left bottom corner watermark on the video
*/
flashvars.watermark = {
plugin: false
}
// flashvars.skipBtn = {
// "skipOffset": "1",
// "label": "Skip Ad"
// }
/*
* ads seem to have their own timers
*/
// flashvars.noticeMessage = {
// "text": "Advertisment {sequenceProxy.timeRemaining|timeFormat}"
// }
flashvars.forceMobileHTML5 = true;
/*
Set up custom video sources
*/
mediaProxy.preferedFlavorBR = -1;
flashvars.mediaProxy = mediaProxy;
/*
Enable hover controls
*/
flashvars.controlBarContainer = {
"plugin" : true,
"hover" : true
}
/*
Disable autoplay on mobile only
*/
flashvars.autoPlay = !kWidget.isMobileDevice();
/*
Enable debugging when needed,
send cross domain headers to prevent console warnings,
prevent ads form playing after video starts from beginning
*/
flashvars.adsOnReplay = true;
flashvars.enableCORS = true;
flashvars.debugMode = false;
flashvars.debugLevel = 0;
flashvars.autoMute = false;
kWidget.embed({
'targetId': 'videoPlayer',
'wid': '_101',
'uiconf_id': '23448200',
'KalturaSupport.LeadWithHTML5':true,
'EmbedPlayer.NativeControls':true,
'EmbedPlayer.CodecPreference' : 'webm',
'flashvars': flashvars,
'readyCallback': function(playerId) {
//initiate click events for the next video on the playlist page
if(playlist)
initiateNextVideo(playerId);
//initiate click events for the dropdown on the playlist page
if(playlist)
initiatePlaylistDropdown(playerId);
var kdp = $('#' + playerId).get(0);
/*
Force autoplay in case of an error on page
Only if it is a preroll ad
*/
if(!kWidget.isMobileDevice() && enablePreroll)
kdp.sendNotification("doPlay");
/*
Ad error events catches
Ads will be skipped automatically on this event, but need to disable the timeout
function [faillock] from rebuilding the player.
*/
kdp.kBind('adErrorEvent', function( qPoint ){
dbug && console.log("\n\n\n\nadErrorEvent\n\n\n\n");
faillock = 0;
// rebuildPlayerWithoutAds(playerId);
});
kdp.kBind('adLoadError', function( qPoint ){
dbug && console.log("\n\n\n\adLoadError\n\n\n\n");
faillock = 0;
rebuildPlayerWithoutAds(playerId);
});
kdp.kBind('mediaError', function( qPoint ){
dbug && console.log("\n\n\nmediaError\n\n\n");
});
kdp.kBind('entryFailed', function( qPoint ){
dbug && console.log("\n\n\nentryFailed\n\n\n");
});
/*
Safeguard against failure - skip to the video if ads fail to play
Enable ad lock check if the ad did start. Wait for it to start playing for 10
seconds and skip it if it doesn't start in this time (since it probably failed to load)
*/
if(!kWidget.isMobileDevice() || type=="optimatic")
kdp.kBind('adStart', function( qPoint ){
dbug && console.log("\n\n\nAd started buffering");
adlimit -= 1;
faillock = 1;
setTimeout(function(){
if(faillock==1){
dbug && console.log("Ad failed, skipping");
// in case if it is a postroll ad and a playlist page, we skip to the next
// video and hope that this time the ad plays.
// else reload the video with no ads
if(playlist && enablePostroll){
rebuildNextVideoElement();
rebuildCurrentVideoElement();
rebuildPlayer(playerId);
}else
rebuildPlayerWithoutAds(playerId);
}
},adtimeout);
});
// enable ad lock check if the ad did start
if(!kWidget.isMobileDevice() || type=="optimatic")
kdp.kBind('onAdPlay', function( start ){
dbug && console.log("Ad started playing");
//disable timeout that will rebuild the video player
faillock = 0;
});
// fire when ad is finished playing
if(enablePreroll)
kdp.kBind('adEnd', function( qPoint ){
adlock += 1;
dbug && console.log("Ad ended. Initiating playlist dropdown.");
});
/*
Since kaltura doesnt have a way to distinuish between
the start of a video or an ad, we need a workaround with counters. ALso
we need to have 2 ways of distinuishing video/ad start end events,
for pre and post roll ads.
*/
kdp.kBind('playbackComplete', function(eventData) {
if(playlist && enablePreroll){
//ad finished playing, and then the video
if(adlock==3){
dbug && console.log("Ad and video finished. Rebuilding player");
adlock = 0;
//update current video element
rebuildCurrentVideoElement();
rebuildNextVideoElement();
rebuildPlayer(playerId);
return;
//there was no ad on this page, so only the video played
}else if(adlock==0){
dbug && console.log("Video finished. Rebuilding player");
adlock = 0;
//update current video element
rebuildNextVideoElement();
rebuildCurrentVideoElement();
rebuildPlayer(playerId);
return;
//the ad finished
}else if(adlock==1){
dbug && console.log("Ad finished.");
}
adlock += 2;
}else if(playlist && enablePostroll){
adlock += 2;
//ad finished playing, and then the video
if(adlock==3){
dbug && console.log("Ad ad video finished. Rebuilding player");
adlock = 0;
//update current video element
rebuildCurrentVideoElement();
rebuildNextVideoElement();
rebuildPlayer(playerId);
return;
//there was no ad on this page, so only the video played
}else if(adlock==2){
dbug && console.log("Video finished. Rebuilding player");
// only rebuild the video if we reached the ad limit because
// in this case noa ds will be loaded after the video, which
// will prevent it from catching the ad end event and reloading the player
adlock = 0;
//update current video element
rebuildNextVideoElement();
rebuildCurrentVideoElement();
rebuildPlayer(playerId);
return;
}
}
});
// fire when ad starts playing
if(enablePostroll)
kdp.kBind('adEnd', function( qPoint ){
adlock += 1;
});
}
});
// delete the video element from DOM and reinitiate later because it
// looked like it made the ads break less often
kWidget.destroy("videoPlayer");
/*
We have reached the max number of times we can display an ad
*/
if(adlimit <= 0){
flashvars.vast = {};
dbug && console.log("Ads play count reached their limit. Disabling ads");
}
// reinitiate kWIdget video player with new settings
kWidget.embed({
'targetId': "videoPlayer",
'wid': '_101',
'uiconf_id': '23448200',
'flashvars': flashvars,
});
}
/*
Sort ad types so that the least broken ones are served first
*/
function sortAdTypes(data){
$xml = $(data);
var types = {};
$xml.find("VASTAdTagURI").each(function(){
var link = $(this).text();
var url = $.getQuery(link,"pageURL");
var isOptimatic = link.indexOf("optimatic.com") != -1;
var isYume = link.indexOf("yumenetworks.com") != -1;
var isBNMLA = link.indexOf("bnmlaX.com") != -1 || link.indexOf("bnmla.com") != -1;
if(isYume)
types.yumenetworks = link;
else if(isBNMLA)
types.bnmla = link;
else if(isOptimatic && url != 0 && url != "[INSERT_PAGE_URL]")
types.optimatic = link;
else
types.other = link;
});
return types;
}
/*
Get the least broken ad type from the sorted at types
types: array of ad links
*/
function selectAdType(types){
if(types==null)
return false;
return types.yumenetworks ? "yumenetworks" :
types.bnmla ? "bnmla" :
types.optimatic ? "optimatic" :
types.other ? "other" :
false;
}
/*
Get the least broken ad link from the sorted at types
types: array of ad links
*/
function selectAdLink(types){
if(types==null)
return false;
return types.yumenetworks ? types.yumenetworks :
types.bnmla ? types.bnmla :
types.optimatic ? types.optimatic :
types.other ? types.other :
false;
}
$.ajax({
type: "GET",
dataType: "XML",
url: 'http://pubads.g.doubleclick.net/gampad/ads?sz=560x315&iu=/14312752/FlashPreRoll&impl=s&gdfp_req=1&env=vp&output=xml_vast2&unviewed_position_start=1&url=http%3A%2F%2Fwww.SOME_SITE.com%2Fnews-article%2Fmediterranean-diet-high-olive-oil-linked-breast-cancer-risk-reduction&correlator=1453083948&description_url=http%253A%252F%252Fwww.SOME_SITE.com%252Fnews-article%252Fmediterranean-diet-high-olive-oil-linked-breast-cancer-risk-reduction&cust_params=PR_URL%3Db4cc6582-c9c1-4a70-9d42-be2e1a49889d%26PR_PageTyp%3Dnews_story%26Gender%3DFemale%26PR_Age%3DSeniors%26PR_Cndtns%3DObesity%2CBreast+Cancer+Female%2CCancer%2CCard' + decodeURIComponent(utcParams),
success: function(data) {
var types = sortAdTypes(data);
var type = selectAdType(types);
var link = selectAdLink(types);
dbug && console.log("Ad link: "+link);
dbug && console.log("Ad type: "+type);
// skip ads if there are no ads in the XML response
if(link==false)
buildVideoPlayer();
else
buildVideoPlayer(link, type);
},
error: function(MLHttpRequest, textStatus, errorThrown) {
dbug && console.log(errorThrown);
}
});
})(jQuery);
</script>
This will, depending on how you set the globals, create a player with custom video urls and either show a pre, or a postroll ad (mobile too, if the ad provider supports it). We host our own kaltura server.
There is extra functionality for enabling playlists with ads, buttons for skipping videos and a timeout I made in case an ad fails so that the player doesn't stall for 30 seconds before playing a video (since Kaltura isn't able to catch this specific error).
The way it works: I make an ajax call to pubads.com, parse the AdVastTag from there and determine the ad provider to decide what parameters to set later. It works for all ad servers that serve VAST and VPAID ads, also mobile ads for providers that support it. What doesn't work is getting our videos to show with DoubleClick ads. The example on Kaltura does work, but they pass their videos into the player with a KMS ID (they uploaded their video to their admin system), but we want to pass the video as a URL, not an ID.
Seems like a lot of people are having issues with this and I heard people saying that you need to modify the back-end to allow Kaltura to play custom urls with doubleclick. Would be good to figure out a solution for everyone to see.
The way it should work is (ideally) by passing the custom video URLs into the mediaProxy and passing the doubleclick ad tag into flashvars.
This part sets the doubleClick ad url.
"doubleClick": {
"plugin": true,
"path": "http://cdnbakmi.kaltura.com/content/uiconf/ps/veria/kdp3.9.1/plugins/doubleclickPlugin.swf",
"adTagUrl": uri,
"leadWithFlash": true,
"disableCompanionAds": true,
},
Has anyone gotten this to work? Any help/suggestions is much appreciated.
Move the "'entryId' : 'url-to-my-video.mp4'," and the sourceType to the root object and it should work.
As it turns out, Kaltura does not support DoubleClick with custom video urls. But there is a way around it - use the generic VAST plugin for your ads, and it will work with mediaProxy for the majority of your ad providers.
Keep in mind, that the generic plugin does not support waterfall ads (that's where one provide calls another provider who calls another one who eventually gives us XML to an ad flash/html5/mp4 file). TO get around this problem, i suggest you come up with a list of providers that have nested ads, then on each ajax call to the pubads.com server, look at the XML and get the adTagUrl. If this element is present, recursively go to that url and repeat the process until it's no longer there. Keep a variable for the previous adTagUrl that you follow to before arriving at an ad file - this is the adTagUrl which will go into the generic VAST preRollUrl/postRollUrl parameter.
Besides that, some providers (like optimatic) will break the player because of an error that is not supported by Kaltura - the ad and the player will break in this case. When this happens, its best to reload the player.
Initiate a timeout once the ad starts buffering (adStart) and cancel it on onAdPlay (which is fired when the ad actually starts playing). If it doesn't reach this point in 12 seconds, reload the player without ads through the kWidget.embed() call.

TVML - Navigate through templates

I´m doing something very wrong, but I´m not very sure of what.
I am trying to create an app which loads a TVML template. From there, you can navigate to a new view (also a template) with information about the selected item. Finally, in this view, you can choose to play the video.
It works until I play the view because it loads but the second screen is on top. I have to "go back" with the menu button to see the video...
Here is my code (simplified of menus and buttons):
application.js
var resourceLoader;
App.onLaunch = function(options) {
var javascriptFiles = [
`${options.BASEURL}/js/ResourceLoader.js`,
`${options.BASEURL}/js/Presenter.js`
];
evaluateScripts(javascriptFiles, function(success) {
if(success) {
resourceLoader = new ResourceLoader(options.BASEURL);
resourceLoader.loadResource(`${options.BASEURL}/templates/Stack.xml.js`, function(resource) {
var doc = Presenter.makeDocument(resource);
doc.addEventListener("select", Presenter.load.bind(Presenter));
Presenter.pushDocument(doc);
});
} else {
var errorDoc = createAlert("Evaluate Scripts Error", "Error attempting to evaluate external JavaScript files.");
navigationDocument.presentModal(errorDoc);
}
});
}
Presenter.js
function getDocument(url) {
var templateXHR = new XMLHttpRequest();
templateXHR.responseType = "document";
templateXHR.addEventListener("load", function() {
pushDoc(templateXHR.responseXML);}, false);
templateXHR.open("GET", url, true);
templateXHR.send();
return templateXHR;
}
function pushDoc(document) {
navigationDocument.pushDocument(document);
}
var Presenter = {
makeDocument: function(resource) {
if (!Presenter.parser) {
Presenter.parser = new DOMParser();
}
var doc = Presenter.parser.parseFromString(resource, "application/xml");
return doc;
},
modalDialogPresenter: function(xml) {
navigationDocument.presentModal(xml);
},
pushDocument: function(xml) {
navigationDocument.pushDocument(xml);
},
load: function(event) {
console.log(event);
var self = this,
ele = event.target,
videoURL = ele.getAttribute("video"),
templateURL = ele.getAttribute("template"),
presentation = ele.getAttribute("presentation");
if(videoURL) {
var player = new Player();
var playlist = new Playlist();
var mediaItem = new MediaItem("video", videoURL);
player.playlist = playlist;
player.playlist.push(mediaItem);
player.present();
}
if(templateURL) {
self.showLoadingIndicator(presentation);
resourceLoader.loadResource(templateURL,
function(resource) {
if (resource) {
var doc = self.makeDocument(resource);
doc.addEventListener("select", self.load.bind(self));
//doc.addEventListener("highlight", self.load.bind(self));
if (self[presentation] instanceof Function) {
self[presentation].call(self, doc, ele);
} else {
self.defaultPresenter.call(self, doc);
}
}
}
);
}
},
showLoadingIndicator: function(presentation) {
if (!this.loadingIndicator) {
this.loadingIndicator = this.makeDocument(this.loadingTemplate);
}
if (!this.loadingIndicatorVisible && presentation != "modalDialogPresenter" && presentation != "menuBarItemPresenter") {
navigationDocument.pushDocument(this.loadingIndicator);
this.loadingIndicatorVisible = true;
}
},
removeLoadingIndicator: function() {
if (this.loadingIndicatorVisible) {
navigationDocument.removeDocument(this.loadingIndicator);
this.loadingIndicatorVisible = false;
}
},
defaultPresenter: function(xml) {
if(this.loadingIndicatorVisible) {
navigationDocument.replaceDocument(xml, this.loadingIndicator);
this.loadingIndicatorVisible = false;
} else {
navigationDocument.pushDocument(xml);
}
},
loadingTemplate: `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<loadingTemplate>
<activityIndicator>
<text>Loading...</text>
</activityIndicator>
</loadingTemplate>
</document>`
}
I also use a ResourceLoader.js file but I think it is not important as it is the one shown in documentation.
When the app launches, I therefore load my first "template" view.
Stack.xml.js
var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<stackTemplate>
<collectionList>
<carousel>
<section>
<lockup>
<img src="${this.BASEURL}/images/main_carousel/main_carousel001.png" width="1740" height="500" />
<overlay>
<title>Title</title>
<subtitle>1902</subtitle>
</overlay>
</lockup>
</section>
</carousel>
<shelf>
<header>
<title>Last Added</title>
</header>
<section>
<lockup template="${this.BASEURL}/templates/Product-001.xml.js" presentation="modalDialogPresenter">
<img src="${this.BASEURL}/images/movies/movie001.png" width="332" height="500" />
<title class="scrollTextOnHighlight">My Title</title>
</lockup>
</section>
</shelf>
</collectionList>
</stackTemplate>
</document>`
}
When click on the image, I load my next template using the template parameter. This template is Product-001.xml.js
var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<productTemplate theme="light">
<shelf>
<header>
<title>Last Added</title>
</header>
<section>
<lockup video="http://trailers.apple.com/movies/focus_features/9/9-clip_480p.mov">
<img src="${this.BASEURL}/resources/images/movies/movie_520_e2.lcr" width="332" height="500" />
<title class="showAndScrollTextOnHighlight">Title 2</title>
</lockup>
</section>
</shelf>
</productTemplate>
</document>`
}
This is using the video parameter. On the first "screen" everything works, no matter if I try to load a Template or a video. However, the same code does´t seem to work on the second screen.
Could someone help me with this?
I don´t know much about Javascript.
I have seen some posts where people say you must push the pages on the stack like this:
var parser = new DOMParser();
var newPageDocument = parser.parseFromString(NEW_PAGE_XML, 'application/xml');
navigationDocument.pushDocument(newPageDocument);
If this is the solution, I would be very grateful if someone could explain me where does that code need to be. Or how can I implement it correctly if I want multiple screens.
Thank you all very much!
Most likely this happens because you are using presentation="modalDialogPresenter" to load Product-001.xml.js file.
Your video may be staying behind the modal, try to see if you can see it when you press Escape.
Then remove that part and test it again.
"modalDialogPresenter" is geared for alerts.
Hey did you finally get the answer to this. I had a similar issue, my video was playing in the background. I could hear the audio but it was "covered" by the current template. The way I fixed this was somewhat of a hack, but i just simply dismissedModally the current view in the function that plays the video. I have a function in my presenter class that plays the video.I don't see this in your code. I think this is what your referring to. Let me know if this helps.
I ended up doing this in the Presenter file.
It seems to work ok:
if(templateURL) {
self.showLoadingIndicator(presentation);
resourceLoader.loadResource(templateURL,
function(resource) {
if (resource) {
var doc = self.makeDocument(resource);
doc.addEventListener("select", self.load.bind(self));
//doc.addEventListener("highlight", self.load.bind(self));
if (self[presentation] instanceof Function) {
// self[presentation].call(self, doc, ele);
self.defaultPresenter.call(self, doc);
}
/* else { self.defaultPresenter.call(self, doc); }
*/
}
}
);
Hope it helps!

HTML5 Video - currentTime not setting properly on iPhone

I have a basic HTML5 video set up from which I load one of four videos. The problem I'm having is that when I load the next video, it continues playing from the previous time position. Efforts to set the currentTime property seem to be either short lived or ignored entirely.
I have added listeners to a collection of events and have something like this in each one;
myPlayer.addEventListener("loadeddata", function() {
console.log(" loadeddata: before = " + myPlayer.currentTime);
myPlayer.currentTime = 0.1;
console.log(" loadeddata: after = " + myPlayer.currentTime);
}, false);
Sometimes I see the time change for one event but not persist correctly;
durationchange: before = 19.773332595825195
durationchange: after = 0.10000000149011612
loadedmetadata: before = 0.10000000149011612
loadedmetadata: after = 19.773332595825195
loadeddata: before = 19.773332595825195
loadeddata: after = 0.10000000149011612
canplay: before = 0.10000000149011612
canplay: after = 19.773332595825195
And sometimes it never even seems to set at all;
durationchange: before = 50.66666793823242
durationchange: after = 50.66666793823242
loadedmetadata: before = 50.66666793823242
loadedmetadata: after = 50.66666793823242
loadeddata: before = 50.66666793823242
loadeddata: after = 50.66666793823242
canplay: before = 50.66666793823242
canplay: after = 50.66666793823242
This seems similar to the issue here but there didn't seem to be any resolution. Has anyone encountered this issue on iPhone before?
TL;DR: change currentTime on the loadeddata event. This works for audio too.
It looks like Safari (and the problem is still appearing for me on Safari 11.1) is Safari will not allow currentTime to be changed when a video is first loaded IF it hasn't loaded a frame for that currentTime yet. The bigger problem: the wrong solution can break Chrome (and likely other browsers too).
Fortunately, we have a lot of events we can listen for while media is loading:
During the loading process of an audio/video, the following events occur, in this order:
loadstart
durationchange
loadedmetadata
loadeddata
progress
canplay
canplaythrough
-W3Schools (I know it's not a preferred source, but I couldn't find the same info on MDN)
I tried adjusting currentTime on different events, but on the first 3 events, Safari would move the time back to 0; and on 5 and 6 it seemed to prevent the video from playing in Chrome, because it would get stuck at currentTime (which I could've worked around, but I thought there was a better solution).
(I didn't want to have to load the whole file to go to the right spot, because I want to support hefty videos. So canplaythrough wasn't an option for me.)
The description for the loadeddata event reads:
The loadeddata event is fired when the first frame of the media has finished loading.
-MDN
When I changed currentTime on loadeddata, Safari could tell that the frame was loaded and available, and would update correctly. It also wouldn't cause Chrome to freeze in a single spot while playing.
The problem and solution are identical for audio.
From my findings the issue seems to be that on iPhone only (iPad works fine) the currentTime property will not be set correctly until the "canplaythrough" event, however changing the currentTime at that point will cause a noticeable hiccup. The solution for that would be to intentionally pause the video after calling load...
myVideo.load();
myVideo.pause();
...and then call play in the event when the time has reset.
The second problem however is when the duration of the new movie is shorter then the currentTime position. In this case not only does currentTime fail to set but "canplaythrough" is never called, and QT just sits at the end of the video doing nothing.
I discovered the solution to both problems was to force a secondary load if the currentTime was not reset in the event BEFORE "canplaythrough". Its a bit round about with the timer callback but it seems to do the trick;
var myVideo = document.getElementById("video1");
myVideo.addEventListener("canplay", function() {
console.log(" canplay: before = " + myVideo.currentTime);
myVideo.currentTime = 0.1;
console.log(" canplay: after = " + myVideo.currentTime);
if( myVideo.currentTime < 1 ) {
myVideo.play();
}
else {
myVideo.load();
myVideo.pause();
setTimeout(checkStarted, 500);
}
}, false);
function checkStarted()
{
console.log(" checkStarted called");
myVideo.play();
}
I had to set the preload attribute to metadata (on the HTML of the main video element) and set the currentTime of the video element within the loadedmetadata event listener.
<video id="myVideo" preload="metadata">
<source src="/path/to/video" type="video/mp4">
</video>
JS
VideoEl.addEventListener('loadedmetadata', VideoMetaDataLoaded);
function VideoMetaDataLoaded() {
VideoEl.currentTime = newTime;
}
Below is my Angular/Ionic solution to restore the video position. It works on IOS, Android, Chrome and Safari.
HTML:
<video preload="metadata"
poster="{{ resource.thumbnail_file }}"
playsinline webkit-playsinline
#videos>
...
</video>
Typescript:
#ViewChildren('videos') videos: QueryList<any>;
videoCache: Map<number, number> = new Map<number, number>();
private restoreVideoPositions() {
var context = this;
setTimeout(() => {
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
var currentTime = context.videoCache.get(video.id);
if (currentTime != null && currentTime > 0) {
console.log('add listener', currentTime);
video.addEventListener("loadeddata", function () {
if (video.readyState >= 3) {
video.currentTime = currentTime;
// video.play();
}
});
video.load();
}
});
}, 0);
}
private storeVideoPositions() {
var context = this;
this.videoCache.clear()
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
video.pause();
context.videoCache.set(video.id, video.currentTime)
});
}
I used a combination of the last two answers here, but had to reduce the ready state limit to 2 (HTMLVideoElement.prototype.HAVE_CURRENT_DATA) as the loadeddata event on iOS would often never come in higher than 2.
With help from the other answers here I came up with the next solution:
HTMLVideoElement.prototype.playFromTime = function(currentTime){
let that = this;
that.load();
that.pause();
that.currentTime = currentTime;
let loadedMetadata;
loadedMetadata = function(event){
that.currentTime = currentTime;
that.removeEventListener("loadedmetadata", loadedMetadata);
}
if(that.currentTime !== currentTime){
that.addEventListener("loadedmetadata", loadedMetadata);
}
that.play();
}
//usage example:
myVidElement.playFromTime(20);
Which in my situation works on an iPhone on Safari, Safari on the desktop, Firefox on macOS, Chrome on macOS. That's all I have tested for now.
Had the same problem with a play video on scroll with React. The following solved it for me.
useEffect(() => {
videoRef.current.load();
}, []);
const { render } = ReactDOM;
const { useState, useEffect, useRef } = React;
const Video = ({ src, height, length }) => {
const [currentTime, setCurrentTime] = useState(0);
const videoRef = useRef(null);
useEffect(() => {
// This is needed for IOS
videoRef.current.load();
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const getCurrentTime = () => {
const percentScrolled = window.scrollY / (height - window.innerHeight);
return length * percentScrolled;
};
const handleScroll = (e) => {
const time = getCurrentTime();
videoRef.current.currentTime = time;
setCurrentTime(time);
};
return (
<div style={{ height }}>
<p>Time: {currentTime.toFixed(2)}s</p>
<video ref={videoRef} muted playsInline>
<source src={src} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
};
const App = () => {
return (
<Video
length={5}
height={3000}
src="https://lqez.github.io/js/airpodsvf/video.mp4"
/>
);
};
render(<App />, document.getElementById("root"));
body {
background: black;
}
p {
color: slategrey;
top: 0;
position: fixed;
z-index: 1;
padding: 20px;
}
video {
top: 60px;
position: fixed;
width: 100%;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
this is a problem with chrome on local assets. happened with me. when i was serving video from local assets and setting time it resets the video to 0.
so the solution that worked for me was serving the video from s3 bucket.

Get artwork_url from Soundcloud API and show album covers in custom SC/SM2 player

I've been trying to sort out how artwork_url can be used from soundclouds API in order to output each cover into this custom player, and have each appropriate thumb next to its own track in the playlist?
I understand that I need to use the artwork_url property, however I do not understand how this is achieved, nor how to integrate it into this particular custom player plugin.
Any code examples in particular and/or help is highly appreciated!
Note: Also would be nice to be able to control the "size" of the artwork as well through other means then just CSS.
Best
EDIT #1
I switched the Soundcloud Custom Player on Heroku since after I was able to get it up and running I discovered it to have a much faster load time in contrast to the original player I cited above (even though that one is still quite awesome)...
Im still posed with the same task now however as before - How to add album art to the script and output accordingly?
Pasted below is the Heroku player:
// # SoundCloud Custom Player
// Make sure to require [SoundManager2](http://www.schillmania.com/projects/soundmanager2/) before this file on your page.
// And set the defaults for it first:
soundManager.url = 'http://localhost:8888/wp-content/themes/earpeacerecords/swf';
soundManager.flashVersion = 9;
soundManager.useFlashBlock = false;
soundManager.useHighPerformance = true;
soundManager.wmode = 'transparent';
soundManager.useFastPolling = true;
// Wait for jQuery to load properly
$(function(){
// Wait for SoundManager2 to load properly
soundManager.onready(function() {
// ## SoundCloud
// Pass a consumer key, which can be created [here](http://soundcloud.com/you/apps), and your playlist url.
// If your playlist is private, make sure your url includes the secret token you were given.
var consumer_key = "915908f3466530d0f70ca198eac4288f",
url = "http://soundcloud.com/poe-epr/sets/eprtistmix1";
// Resolve the given url and get the full JSON-worth of data from SoundCloud regarding the playlist and the tracks within.
$.getJSON('http://api.soundcloud.com/resolve?url=' + url + '&format=json&consumer_key=' + consumer_key + '&callback=?', function(playlist){
// I like to fill out the player by passing some of the data from the first track.
// In this case, you'll just want to pass the first track's title.
$('.title').text(playlist.tracks[0].title);
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + track.title + '</li>').data('track', track).appendTo('.tracks');
// * Get appropriate stream url depending on whether the playlist is private or public.
// * If the track includes a *secret_token* add a '&' to the url, else add a '?'.
// * Finally, append the consumer key and you'll have a working stream url.
url = track.stream_url;
(url.indexOf("secret_token") == -1) ? url = url + '?' : url = url + '&';
url = url + 'consumer_key=' + consumer_key;
// ## SoundManager2
// **Create the sound using SoundManager2**
soundManager.createSound({
// Give the sound an id and the SoundCloud stream url we created above.
id: 'track_' + track.id,
url: url,
// On play & resume add a *playing* class to the main player div.
// This will be used in the stylesheet to hide/show the play/pause buttons depending on state.
onplay: function() {
$('.player').addClass('playing');
$('.title').text(track.title);
},
onresume: function() {
$('.player').addClass('playing');
},
// On pause, remove the *playing* class from the main player div.
onpause: function() {
$('.player').removeClass('playing');
},
// When a track finished, call the Next Track function. (Declared at the bottom of this file).
onfinish: function() {
nextTrack();
}
});
});
});
// ## GUI Actions
// Bind a click event to each list item we created above.
$('.tracks li').live('click', function(){
// Create a track variable, grab the data from it, and find out if it's already playing *(set to active)*
var $track = $(this),
data = $track.data('track'),
playing = $track.is('.active');
if (playing) {
// If it is playing: pause it.
soundManager.pause('track_' + data.id);
} else {
// If it's not playing: stop all other sounds that might be playing and play the clicked sound.
if ($track.siblings('li').hasClass('active')) { soundManager.stopAll(); }
soundManager.play('track_' + data.id);
}
// Finally, toggle the *active* state of the clicked li and remove *active* from and other tracks.
$track.toggleClass('active').siblings('li').removeClass('active');
});
// Bind a click event to the play / pause button.
$('.play, .pause').live('click', function(){
if ( $('li').hasClass('active') == true ) {
// If a track is active, play or pause it depending on current state.
soundManager.togglePause( 'track_' + $('li.active').data('track').id );
} else {
// If no tracks are active, just play the first one.
$('li:first').click();
}
});
// Bind a click event to the next button, calling the Next Track function.
$('.next').live('click', function(){
nextTrack();
});
// Bind a click event to the previous button, calling the Previous Track function.
$('.prev').live('click', function(){
prevTrack();
});
// ## Player Functions
// **Next Track**
var nextTrack = function(){
// Stop all sounds
soundManager.stopAll();
// Click the next list item after the current active one.
// If it does not exist *(there is no next track)*, click the first list item.
if ( $('li.active').next().click().length == 0 ) {
$('.tracks li:first').click();
}
}
// **Previous Track**
var prevTrack = function(){
// Stop all sounds
soundManager.stopAll();
// Click the previous list item after the current active one.
// If it does not exist *(there is no previous track)*, click the last list item.
if ( $('li.active').prev().click().length == 0 ) {
$('.tracks li:last').click();
}
}
});
});
EDIT #2
So I strangely was able to work something out... I have no clue if its semantically correct however...
$.getJSON('http://api.soundcloud.com/resolve?url=' + url + '&format=json&consumer_key=' + consumer_key + '&callback=?', function(playlist){
// I like to fill out the player by passing some of the data from the first track.
// In this case, you'll just want to pass the first track's title.
$('.title').text(playlist.tracks[0].title);
$('.album_art').attr('src', playlist.artwork_url);
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + '<img src="' + playlist.artwork_url + '">' + track.title + '</li>').data('track', track).appendTo('.tracks');
// * Get appropriate stream url depending on whether the playlist is private or public.
// * If the track includes a *secret_token* add a '&' to the url, else add a '?'.
// * Finally, append the consumer key and you'll have a working stream url.
url = track.stream_url;
(url.indexOf("secret_token") == -1) ? url = url + '?' : url = url + '&';
url = url + 'consumer_key=' + consumer_key;
// ## SoundManager2
// **Create the sound using SoundManager2**
soundManager.createSound({
// Give the sound an id and the SoundCloud stream url we created above.
id: 'track_' + track.id,
url: url,
// On play & resume add a *playing* class to the main player div.
// This will be used in the stylesheet to hide/show the play/pause buttons depending on state.
onplay: function() {
$('.player').addClass('playing');
$('.title').text(track.title);
},
onresume: function() {
$('.player').addClass('playing');
},
// On pause, remove the *playing* class from the main player div.
onpause: function() {
$('.player').removeClass('playing');
},
// When a track finished, call the Next Track function. (Declared at the bottom of this file).
onfinish: function() {
nextTrack();
}
});
});
EDIT #3
Below is the HTML and CSS markup that works with the player for better clarification...
HTML
<div class='title'></div>
<a class='prev'>Previous</a>
<a class='play'>Play</a>
<a class='pause'>Pause</a>
<a class='next'>Next</a>
</div>
CSS
/*
-------------------------------------------------------------------------
Soundcloud Player
-------------------------------------------------------------------------
*/
#sticky_header #sticky_content .player {
height: 570px;
overflow: hidden;
}
#sticky_header #sticky_content .tracks {
}
#sticky_header #sticky_content .tracks li {
cursor: pointer;
height: 40px;
text-align: left;
}
#sticky_header #sticky_content .tracks li img.album_art {
width: 40px;
height: 40px;
border-radius: 5px;
margin-right: 15px;
}
#sticky_header #sticky_content .title {
}
#sticky_header #sticky_content .prev {
}
#sticky_header #sticky_content .play {
display: block;
}
#sticky_header #sticky_content .playing .play {
display: none;
}
#sticky_header #sticky_content .pause {
display: none;
}
#sticky_header #sticky_content .playing .pause {
display: block;
}
#sticky_header #sticky_content .next {}
to get an image you can use this code:
//get element by id from your iframe
var widget = SC.Widget(document.getElementById('soundcloud_widget'));
widget.getCurrentSound(function(music){
artwork_url = music.artwork_url.replace('-large', '-t200x200');
$('#song1').css('background', 'url(\"'+artwork_url+'\") ');
});
normaly you get a link with "-large" at the end and the size is 100x100. If you want other sizes you have to change the end with ".replace" like I did. A list with available sizes can you find here:
https://developers.soundcloud.com/docs/api/reference#tracks
(my size 200x200 is not listed but works. Maybe there are more sizes like every hundred px.)
at the moment the code works only for the actual playing song. For me it's not a solution, because i need all images from my playlist.
Here's where iterating over the tracks retrieved from the API happeninng:
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + track.title + '</li>').data('track', track).appendTo('.tracks');
Inside of the iterator function you can now access track.artwork_url and possibly set it as a background image or perhaps background for some element, maybe something like:
$('<li><img src=" + track.artwork_url + "></img>' + track.title + '</li>').data('track', track).appendTo('.tracks');
I hope this helps.
UPD. In your updated code, you should refer to track.artwork_url instead of playlist – then you'll get each track's individual artwork.

Creating a fake image for a CKEditor custom Plugin

I am developing a plugin for video embedding, I put this code when the plugin dialog OK button is clicked.
var embedCode =
'<iframe title="YouTube video player" class="youtube-player" type="text/html"' +
width="' + width + '" height="' + height + '" src="http://www.youtube.com/embed/' + textField + '?rel=0"' +
frameborder="0" width="620" height="200" style="width:' + width + 'px; height:' + height + 'px">' +
'</iframe>';
this.getParentEditor().insertHtml(embedCode);
Now when double click on the iframe in the editor open the iframe properties dialog not my plugin dialog.
How I can develop a fake image for my custom plugin.
I have found a solution for it.
Ckeditor make fake elements from the code generated by the plugins. When the editor loads it takes the code and convert it to fake elements and the default editing is worked on that fake elements the code for it will be defined in the
afterInit : function( editor )
{
function of the the editor and will be called in on OK event of the edior
onOk : function()
{
var embedCode = updatePreview( this,true );
var newFakeImage = editor.createFakeElement( embedCode, 'cke_audio', 'audio', true );
The Code for the fake element example is the following, I have created for an audio embed code plugin
afterInit : function( editor )
{
function createFakeElement( editor, realElement )
{
return editor.createFakeParserElement( realElement, 'cke_audio', 'audio', true );
}
var dataProcessor = editor.dataProcessor,
dataFilter = dataProcessor && dataProcessor.dataFilter;
if ( dataFilter )
{
dataFilter.addRules(
{
elements :
{
'div' : function( element )
{
//alert("here");
var attributes = element.attributes;
if( attributes.class == 'audio' ){
//alert("here");
return createFakeElement( editor, element );
}
return null;
}
}
},
5);
}
}