YouTube embed API issue on IOS and Android - javascript

An issues popped up in the last few days with the YouTube embed API. The issue is that when you embed a video with the official API, it simply doesn't allow you to access to the API. When you try to access to the API, you got error message on the log (IOS) and if you try to play the video through the API the video blacks out. If you load it via the API, but you do not use the API, the user is able to play the video with tap.
The issue persist on the following browsers:
IOS 7 Safari on iPad and iPhone
IOS 7 Chrome on iPad and iPhone
Android 4 Chrome
(My play button uses the API to play the video and that produce the error)
JSfiddle: http://jsfiddle.net/frdd8nvr/6/
Error message:
Unable to post message to https://www.youtube.com. Recipient has origin http://fiddle.jshell.net.
postMessage[native code]:0
Jwww-widgetapi.js:26:357
Nwww-widgetapi.js:25
(anonymous function)[native code]:0
html5player.js:1201:97
Blocked a frame with origin "https://www.youtube.com" from accessing a frame with origin "http://jsfiddle.net". The frame requesting access has a protocol of "https", the frame being accessed has a protocol of "http". Protocols must match.
Some debug info:
As I see the API create the iframe on the site. The src is sometime http and sometime https.
http://www.youtube.com/embed/ZPy9VCovVME?enablejsapi=1&origin=http%3A%2F%2Ffiddle.jshell.net&autoplay=0&modestbranding=1&wmode=opaque&forceSSL=false
My test showed that most of the times YouTube servers simply LOCATION: https://... the request to the https url, but around 10% they served the http request with proper content.
I think somehow the issue related with the forced https, but I was not able to figure out the solution.
Have you experienced the same? Do you have some kind of solution for this problem? Is it a YouTube bug?
My test code:
<div id="myvideo"></div>
<button id="play-button">Play</button>
JS:
var tag = document.createElement("script");
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
onYouTubeIframeAPIReady = function () {
var vars = {
enablejsapi: 1,
origin: window.location.protocol + "//" + window.location.host,
autoplay: 0,
modestbranding: 1,
wmode: "opaque",
forceSSL: false
};
if (+(navigator.platform.toUpperCase().indexOf('MAC') >= 0 && navigator.userAgent.search("Firefox") > -1)){
vars.html5 = 1;
}
var playerobj = new YT.Player('myvideo', {
videoId: 'ZPy9VCovVME',
wmode: 'opaque',
playerVars: vars,
events: {
onReady: function(){
$('#play-button').on('click', function(){
playerobj.playVideo();
});
//playerobj.playVideo();
},
onStateChange: function(state){
switch(state.data){
case YT.PlayerState.PLAYING:
break;
//case YT.PlayerState.PAUSED:
case YT.PlayerState.ENDED:
break;
}
}
}
});
}

First, I think this is a duplicate of the following question: YouTube IFrame API play method doesn't work before touch on some Android tablets
I could reproduce the same problem on the YouTube Player Demo page.
The error
Unable to post message to https://www.youtube.com. Recipient has origin http://fiddle.jshell.net.
is only occuring on your jsfiddle. On the demo page this error is not occuring, therefore your observed bug seems not to be related to the error logged in the console. I checked this with chrome on Android 4.4.4.
Workaround
I have a dirty workaround which works on chrome on Android 4.4.4 (Nexus5) and 4.1.2 (S2). I have no iOS Device to test this. The workaround works on my phones, because the video always starts the second time I click the play-button.
http://jsfiddle.net/kjwpwpx8/6/
I can always start the video on click-Events after the playVideo function was called once before. In my workaround I put two iframes on the page. One is hidden while the other one is visible. If the page is viewed by a mobile browser I call the playVideo function of the second video after the OnReady-Event. The video won't play as the browser blocks this.
Now when our play-button is pressed the first video becomes hidden and stopped and the second video becomes visible and the playVideo function is called again.
Here is the important code:
var playerOptions = {
videoId: 'ZPy9VCovVME',
wmode: 'opaque',
playerVars: vars,
events: {}
};
var playerobj1 = new YT.Player('myvideo1', playerOptions);
var playerobj2 = null;
playerOptions.events.onReady = function(){
$('#play-button').on('click', function(){
$("#videowrapper .video1wrapper").hide();
playerobj1.stopVideo();
$("#videowrapper .video2wrapper").show();
playerobj2.playVideo();
});
if(isMobileBrowser)
playerobj2.playVideo();
};
playerobj2 = new YT.Player('myvideo2', playerOptions);

Related

Speech gets cut off in firefox when page is auto-refreshed but not in google chrome

I have this problem where in firefox the speech gets cut off if the page is auto-refreshed, but in google chrome it finishes saying the speech even if the page is auto-refreshed. How do I fix it so that the speech doesn't get cut off in firefox even when the page is auto-refreshed?
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
window.speechSynthesis.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = 750;
setTimeout(function () {
myframe.attr({ src: location.href });
}, interval);
});
}
})(jQuery);
I have this problem where in firefox the speech gets cut off if the
page is auto-refreshed, but in google chrome it finishes saying the
speech even if the page is auto-refreshed.
The described behaviour for Firefox is a sane expected implementation.
Browsing the source code of Firefox and Chromium the implementation of speechSynthesis.speak() is based on a socket connection with the local speech server. That server at *nix is usually speech-dispatcher or speechd (speech-dispatcher). See How to programmatically send a unix socket command to a system server autospawned by browser or convert JavaScript to C++ souce code for Chromium? for description of trying to implement SSML parsing at Chromium.
Eventually decided to write own code to achieve that requirement using JavaScript according to the W3C specification SpeechSynthesisSSMLParser after asking more than one question at SE sites, filing issues and bugs and posting on W3C mailings lists without any evidence that SSML parsing would ever be included as part of the Web Speech API.
Once that connection is initiated a queue is created for calls to .speak(). Even when the connection is closed Task Manager might still show the active process registered by the service.
The process at Chromium/Chrome is not without bugs, the closest that have filed to what is being described at the question is
Issue 797624: "speak speak slash" is audio output of .speak() following two calls to .speak(), .pause() and .resume()
Why hasn't Issue 88072 and Issue 795371 been answered? Are Internals>SpeechSynthesis and Blink>Speech dead? (for possible reason why "but in google chrome it finishes saying the speech even if the page is auto-refreshed." is still possible at Chrome)
.volume property issues
Issue 797512: Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Chromium/Chrome)
Bug 1426978 Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Firefox)
The most egregious issue being Chromium/Chrome webkitSpeechReconition implementation which records the users' audio and posts that audio data to a remote service, where a transcript is returned to the browser - without explicitly notifying the user that is taking place, marked WONT FIX
Issue 816095: Does webkitSpeechRecognition send recorded audio to a remote web service by default?
Relevant W3C Speech API issues at GitHub
The UA should be able to disallow speak() from autoplaying #27
Precisely define when speak() should fail due to autoplay rules #35 (ironically, relevant to the reported behaviour at Chromium/Chrome and output described at this question, see Web Audio, Autoplay Policy and Games and Autoplay Policy Changes)
Intent to Deprecate: speechSynthesis.speak without user activation
Summary
The SpeechSynthesis API is actively being abused on the web. We don’t have hard data on abuse, but since other autoplay avenues are
starting to be closed, abuse is anecdotally moving to the Web Speech
API, which doesn't follow autoplay rules.
After deprecation, the plan is to cause speechSynthesis.speak to
immediately fire an error if specific autoplay rules are not
satisfied. This will align it with other audio APIs in Chrome.
Timing of SpeechSynthesis state changes not defined #39
Timing of SpeechSynthesisUtterance events firing not defined #40
Clarify what happens if two windows try to speak #47
In summary, would not describe the behaviour at Firefox as a "problem", but the behaviour at Chrome as being a potential "problem".
Diving in to W3C Web Speech API implementation at browsers is not a trivial task. For several reasons. Including the apparent focus, or available option of, commercial TTS/SST services and proprietary, closed-source implementations of speech synthesis and speech recognition in "smart phones"; in lieu of fixing the various issues with the actual deployment of the W3C Web Speech API at modern browsers.
The maintainers of speechd (speech-dispatcher) are very helpful with regards to the server side (local speech-dispatcher socket).
Cannot speak for Firefox maintainers. Would estimate it is unlikely that if a bug is filed relevant to the feature request of continuing execution of audio output by .speak() from reloaded window is consistent with recent autoplay policies implemented by browsers. Though you can still file a Firefox bug to ask if audio output (from any API or interface) is expected to continue during reload of the current window; and if there are any preferences or policies which can be set to override the described behaviour, as suggested at the answer by #zip. And get the answer from the implementers themselves.
There are individuals and groups that compose FOSS code which are active in the domain and willing to help SST/TTS development, many of which are active at GitHub, which is another option to ask questions about how to implement what you are trying to achieve specifically at Firefox browser.
Outside of asking implementers for the feature request, you can read the source code and try create one or more workarounds. Alternatives include using meSpeak.js, though that still does not necessarily address if Firefox is intentionally blocking audio output during reload of the window.
Not sure why there's a difference in behavior... guest271314 might be on to something in his answer. However, you may be able to prevent FF from stopping the tts by intercepting the reload event with a onbeforeunload handler and waiting for the utterance to finish:
msg = new SpeechSynthesisUtterance("say something");
window.speechSynthesis.speak(msg);
window.onbeforeunload = function(e) {
if(window.speechSynthesis.speaking){
event.preventDefault();
msg.addEventListener('end', function(event) {
//logic to continue unload here
});
}
};
EDITED: See more elegant solution with promises below initial answer!
Below snippet is a workaround to the browser inconsistencies found in Firefox, checking synth.speaking in the interval and only triggering a reload if it's false prevents the synth from cutting of prematurely:
(It does not NOT work properly in the SO snippet, I assume it doesn't like iFrames in iFrames or whatever, just copy paste the code in a file and open it with Firefox!)
<p>I'm in the body, but will be in an iFrame</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var synth = window.speechSynthesis;
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
synth.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = setInterval(function () {
if (!synth.speaking) {
myframe.attr({ src: location.href });
clearInterval(interval);
}
}, 750);
});
}
})(jQuery);
</script>
A more elaborate solution could be to not have any setTimeout() or setInterval() at all, but use promises instead. Like this the page will simply reload whenever the message is done synthesizing, no matter how short or long it is. This will also prevent the "double"/overlapping-speech on the initial pageload. Not sure if this helps in your scenario, but here you go:
<button id="toggleSpeech">Stop Speaking!</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
if (window == window.top) {
window.speech = {
say: function(msg) {
return new Promise(function(resolve, reject) {
if (!SpeechSynthesisUtterance) {
reject('Web Speech API is not supported');
}
var utterance = new SpeechSynthesisUtterance(msg);
utterance.addEventListener('end', function() {
resolve();
});
utterance.addEventListener('error', function(event) {
reject('An error has occurred while speaking: ' + event.error);
});
window.speechSynthesis.speak(utterance);
});
},
speak: true,
};
}
(function($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var $iframe = $(this).contents();
$iframe.find('#toggleSpeech').on('click', function(e) {
console.log('speaking will stop when the last sentence is done...');
window.speech.speak = !window.speech.speak;
});
window.speech.say('please finish saying this entire sentence.')
.then(function() {
if ( window.speech.speak ) {
console.log('speaking done, reloading iframe!');
myframe.attr({ src: location.href });
}
});
});
}
})(jQuery);
</script>
NOTE: Chrome (since v70) does NOT allow the immediate calling of window.speechSynthesis.speak(new SpeechSynthesisUtterance(msg)) anymore, you will get an error speechSynthesis.speak() without user activation is no longer allowed..., more details here. So technically the user would have to activate the script in Chrome to make it work!
Firefox:
First of all type and search for the “about: config” inside the browser by filling it in the address bar. This will take to another page where there will be a pop up asking to Take Any Risk, you need to accept that. Look for the preference named “accessibility.blockautorefresh” from the list and then right-click over that. There will be some options appearing as the list on the screen, select the Toggle option and then set it to True rather than False. This change will block the Auto Refresh on the Firefox browser. Remember that this option is revertable!

JavaScript play() function does not working in Chrome

I created an audio object and want to play it when user leave the window. So, my code is here:
$('body').on('mouseleave', function(){
var audio = new Audio( 'quite-impressed.mp3' )
audio.play()
})
It works well in Firefox. It also works in Chrome if I click in the page and then leave mouse outside of the body. But, when I leave the mouse without clicking in the page an error showed in the console and the audio does not play
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first. https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
But, in this example site it works fine without interacting with the page. How can I make it possible? Thanks in advance.
It seems they use an AudioContext to play that sound.
Chrome did came back a few months ago about blocking the AudioContext API, because a lot of fair uses were not prepared for such restriction and thus got broken by it.
But M71, which will get released in December 2018 will reenable that restriction, you can read about it here: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio
// this will work in Chrome < 70 but not af
onmouseout = e => {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
osc.connect(ctx.destination);
osc.start(0);
osc.stop(1);
}
Outsourced live example, since Stacksnippets are always granted the user gesture anyway: https://jsfiddle.net/zy3ev8ka/
Try this:
window.audio = new Audio( 'quite-impressed.mp3' )
$('body').on('mouseleave', function(){
audio.play()
})
This worked for me on Chrome 77:
On address bar: chrome://settings/content/sound
Turn off "Allow sites to play sound (recommended)"
Turn it on again
if your js play function is not running and if your code is correct then this may help,you have to allow your browser to get that sound file
just goto settings =>privacy and security => site-settings =>sound.
and then add your local url at add to play section..

HTML5 Javascript/jQuery Video MEDIA_ERR_NETWORK on localhost

I have a web application which is intermittently producing an error when using Chrome 30.0.1599.101 m.
I am using the HTML5 <video /> tag and controlling the src attribute using javascript.
The page which is causing the error sometimes errors on the first video or on the seventh. There is no predictable pattern.
Here is the javascript which handles the src:
var playing = false;
var media = $('#video')[0];
function initModule() {
$.ajax({
url: recap,
type:'HEAD',
error:
function(){
screenNotify('Error!', false, "404: Module video content could not be found." + recap, true);
},
success:
function() {
media.src = recap;
registerListeners(media);
media.load();
}
});
}
function registerListeners(listen) {
listen.addEventListener('ended', hide_recap);
listen.addEventListener('error', mediaError);
}
function mediaError(event) {
screenNotify('Media Error!', false, "Media failed with code: " + event.currentTarget.error.code, true);
}
function play_recap() {
if (!playing) {
playing = true;
media.play();
}
}
function hide_recap() {
if (playing) {
playing = false;
media.pause();
media.currentTime = 0.0;
}
}
Interestingly, there is no error thrown when I call media.load(), instead, you need to look at the network requests to see that the GET has actually produced a result of (failed).
Another thing to note, is this GET status only occurs for videos which reside within this particular folder location: /interactive/vids/recap/. The error does not occur anywhere else within the application.
Finally, the last thing which is strange about this error, is when the application finally attempts to play the video from play_recap(), about 1 second of the video will actually play, followed by an error being thrown on the video element.
The error is MEDIA_ERR_NETWORK however this application is installed locally on a Tomcat server and is running under localhost.
So why is the GET request producing (failed) and the HTML5 <video> element producing a MEDIA_ERR_NETWORK when everything is only ever running on localhost?
I've noticed very similar issue with Chrome 31. I'm getting inconsistently MEDIA_ERR_NETWORK, all other tested browsers work fine.
I haven't found any other solution than just retrying - you could try recreate the video element or just change currentTime - it always helps in my case.

Youtube iframe API fails to post message

For awhile now, a piece of javascript I wrote which listens to youtube actions on a certain page worked wonderfully. I am using Youtube's iframe js api: https://developers.google.com/youtube/iframe_api_reference .
But one recent content addition, a specific youtube video, the tracking wouldn't work. The events won't fire at all.
In the console, I noticed this post message error:
Unable to post message to http://youtube.com. Recipient has origin http://www.youtube.com.
So nothing with my own code helped. Some questions here on stackoverflow suggested this is an issue with initiating new YT.player too soon, so I tried a whole bunch of things like loading the yt js api file on window load and only apply the api after, but that didn't seem to do any good either.
I know this post is 3 years old, but for those who are still searching for an answer:
Add this script and everything works fine:
<script src="https://www.youtube.com/iframe_api"></script>
I've had the same problem with jwplayer and fixed it with that script.
It took me over an hour, but the answer was right in front of me. It's actually pretty self explained: You cannot use youtube's js api to track an iframe video without www. I don't know why, it certainly does not say so in their documentation.
I tested this a few times and confirmed, as of now, tracking an iframe with the source www.youtube.com/embed/0GN2kpBoFs4 would work wonderfully while tracking youtube.com/embed/0GN2kpBoFs4 will throw:
Unable to post message to http://youtube.com. Recipient has origin http://www.youtube.com.
The confusing part of course is that both the video load and play fine. It's only the API which is not working properly.
fiddle - http://jsfiddle.net/8tkgW/ (Tested on chrome / mountain lion)
Btw, while writing this answer I came across YouTube iframe API: how do I control a iframe player that's already in the HTML? - notice this guy's fiddle. He wrote his own youtube iframe implementation (wow!). If you change the iframe source address in the fiddle to one without www, it will work. This only means youtube writes bad js. Bad bad bad!
Don't forget to add it to the whitelist:
<!-- Add the whitelist plugin -->
<plugin name="cordova-plugin-whitelist" source="npm" spec="*"/>
<!-- White list https access to Youtube -->
<allow-navigation href="https://*youtube.com/*"/>
The youtube api documentation recommend to load the api like this
var tag = document.createElement('script');
tag.src = "http://youtube.com/iframe_api";
tag.id = "youtubeScript";
var firstScriptTag = document.getElementsByTagName('script')[1];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
But getting this error:
Unable to post message to http://youtube.com. Recipient has origin http://www.youtube.com
Here is the best solution I found from [a now-dead site]:
So add this first at the top before calling the Api
if (!window['YT']) {var YT = {loading: 0,loaded: 0};}
if (!window['YTConfig']) {var YTConfig = {'host': 'http://www.youtube.com'};}
if (!YT.loading) {YT.loading = 1;(function(){var l = [];YT.ready = function(f) {if (YT.loaded) {f();}
else
{l.push(f);}};
window.onYTReady = function() {YT.loaded = 1;for (var i = 0; i < l.length; i++) {try {l[i]();} catch (e) {}}};
YT.setConfig = function(c) {for (var k in c) {if (c.hasOwnProperty(k)) {YTConfig[k] = c[k];}}};
var a = document.createElement('script');
a.type = 'text/javascript';
a.id = 'www-widgetapi-script';
a.src = 'https:' + '//s.ytimg.com/yts/jsbin/www-widgetapi-vflumC9r0/www-widgetapi.js';
a.async = true;
var b = document.getElementsByTagName('script')[0];
b.parentNode.insertBefore(a, b);})();}
//===========THEN=============================
function onYouTubeIframeAPIReady () {// do stuff here}

youtube iframe api not working in firefox with backbone

So i'm trying to implement youtube API in backbone code.
I put the API in a view and try to run everything from there
It works great in chrome but isen't running in firefox, the event that's suppose to trigger when the youtube API is done loading isen't fired. If even tried putting alert in there to see if it was context related, this alert would run in chrome but not in firefox.
Anyone seen anything like this before?
I'm using backbone 0.9 and the youtube iframe api
live example: http://alpha.mychannls.com/channel/8
relevant code
App.Views.youtubePlayer = Backbone.View.extend({
defaults: {
'wmode': 'transparent',
'height': '420',
'width': '740',
'volume': '40',
'playerVars': {
'wmode': 'transparent',
'origin': 'http://www.youtube.com',
'enablejsapi': 1,
'autoplay': 0,
'controls': 1,
'iv_load_policy': 3,
'showinfo': 0,
'rel': 0,
'allowfullscreen': 1,
'allowtransparency': 'yes'
}
},
initialize: function(options) {
//bind youtubeplay event to play method
this.collection.bind('youtubePlay', this.play, this);
//bind youtubestop to stop method
this.collection.bind('youtubeStop', this.stop, this);
//extend this.o so the options are globally accessable
this.o = $.extend(true, this.defaults, options);
this.render();
},
render: function() {
//create a new youtube player
var view = this;
console.log("this is run by firefox");
this.ytplayer = new window.YT.Player('ytplayer', {
'events': {
'onReady': view.onPlayerReady,
'onError': view.onError,
'onStateChange': view.onPlayerStateChange
}
});
return this;
},
//when youtube player is ready to run
onPlayerReady: function(e){
console.log('this function isent run in firefox');
//start the first video
App.videos.youtubeready();
}
});
outside the backbone code because youtube api player needs to be declared global
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
function onYouTubeIframeAPIReady() {
App.youtubeplayer = new App.Views.youtubePlayer( { el: '#ytplayer' , collection : App.videos } );
}
entire code can be found here
http://alpha.mychannls.com/js/channel.js
I think I found the answer to the problem. I implemented this and it actually works great now. Here's the gist of it:
Firefox has 2 separate implementations of an IFrame. The first implementation is before the onload event is triggered by the browser. The second is after the onload event. What this means for Backbone/Angular or other event driven javascript application frameworks that operate after the onload event is the Youtube IFrame API implementation of adding the IFrame dynamically to your page will not work.
The solution I found was also located on in the YT IFrame API documentation, but you wouldn't know it.
https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player
See the section after the JS, about creating the IFrame tag yourself? It is exactly right! So all you need to do is have the IFrame in your page when the page is sent from the server (ie: your index.html) or whichever main file you're using.
In your case, it looks like you've got a Backbone/Marionette app, so this solution should work fine for you.
<iframe id="player" type="text/html" width="1" height="1"
src="http://www.youtube.com/embed/?enablejsapi=1&origin=http://example.com"
frameborder="0"></iframe>
Something like the above code placed inside your main document and referenced from the Backbone app will work.
Good luck, just respond back if you need more help.
Jeff: FWIW - this IFrame/Firefox problem has existed since at least 2005 (the date of the obscure post I found). You might want to note it in the IFrame documentation for dynamic single page Javascript application developers, as this isn't easy to debug.

Categories

Resources