Reputation: 7921
I have a Youtube iFrame and I control playback using keyboard shortcuts. However, when I fast-forward/rewind, the focus changes to the iFrame, which captures subsequent key presses. Thus, I use a simple function to poll when focus changes to the iFrame, and then switch the focus back to the document body. Here is the live code:
However, it doesn't work! Even though document.body.focus()
is being called, the focus never changes from the iFrame. Any idea how to fix this?
Note: The browser I'm using is Chrome 41.0.2272.118 on my Surface Pro 3 (Win 8.1)
Upvotes: 2
Views: 1707
Reputation: 1
NOTE THE SOLUTION IS WITH RELATIVE TO https://pub.dev/packages/youtube_player_iframe PACKAGE BUT IT MIGHT WORK FOR OTHER PACKAGES ALSO AS ALL USES YOUTUBE IFRAME EMBEDDINGS
inject focus releaser which runs for evey youtube frame i think like below :
_controller.webViewController.runJavaScript('window.focus(););
i know its not allowed to access webViewController outside package file but it works! You can debug and see in console yourself by doing:
_controller.webViewController.runJavaScript('window.focus();console.log("focus released");');
where _controller is YoutubePlayerController.
Now the focus is released to window not the flutter i think. Now to make a custom listener for the player what i did is modified the "~\Pub\Cache\hosted\pub.dev\youtube_player_iframe-5.2.1\assets\player.html" file of the package like below (basically i added a document.listener) :
<!DOCTYPE html>
<html lang="en">
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<style>
html {
width: 100%;
height: 100%;
background-color: black;
pointer-events: <<pointerEvents>>;
}
body {
margin: 0;
width: 100%;
height: 100%;
background-color: black;
pointer-events: inherit;
}
.embed-container iframe,
.embed-container object,
.embed-container embed {
position: absolute;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
pointer-events: inherit;
}
</style>
<title>Youtube Player</title>
</head>
<body>
<div class="embed-container">
<div id="<<playerId>>"></div>
</div>
<script>
var tag = document.createElement("script");
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var platform = "<<platform>>";
var host = "<<host>>";
var player;
var timerId;
// Add keybindings for seek controls
document.addEventListener('keydown', function (event) {
switch (event.key) {
case 'z':
seekVideo(-2); // Seek backward by 2 seconds
break;
case 'c':
seekVideo(2); // Seek forward by 2 seconds
break;
case 'a':
seekVideo(-5); // Seek backward by 5 seconds
break;
case 'd':
seekVideo(5); // Seek forward by 5 seconds
break;
case 'q':
seekVideo(-10); // Seek backward by 10 seconds
break;
case 'e':
seekVideo(10); // Seek forward by 10 seconds
break;
case 'space':
if (player.getPlayerState() == 1) {
player.pauseVideo();
} else {
player.playVideo();
}
break;
}
});
// Function to seek the video by a specified duration
function seekVideo(duration) {
var currentTime = player.getCurrentTime();
var newTime = currentTime + duration;
if (newTime < 0) newTime = 0; // Prevent negative time
if (newTime > player.getDuration()) newTime = player.getDuration(); // Prevent exceeding video duration
player.seekTo(newTime, true);
}
function onYouTubeIframeAPIReady() {
player = new YT.Player("<<playerId>>", {
host: host,
playerVars: <<playerVars>>,
events: {
onReady: function (event) {
handleFullScreenForMobilePlatform();
sendMessage('Ready', event);
},
onStateChange: function (event) {
clearTimeout(timerId);
sendMessage('StateChange', event.data);
if (event.data == 1) {
timerId = setInterval(function () {
var state = {
'currentTime': player.getCurrentTime(),
'loadedFraction': player.getVideoLoadedFraction()
};
sendMessage('VideoState', JSON.stringify(state));
}, 100);
}
},
onPlaybackQualityChange: function (event) {
sendMessage('PlaybackQualityChange', event.data);
},
onPlaybackRateChange: function (event) {
sendMessage('PlaybackRateChange', event.data);
},
onApiChange: function (event) {
sendMessage('ApiChange', event.data);
},
onError: function (event) {
sendMessage('PlayerError', event.data);
},
onAutoplayBlocked: function (event) {
sendMessage('AutoplayBlocked', event.data);
},
},
});
player.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('message', (event) => {
try {
var data = JSON.parse(event.data);
if(data.function){
var rawFunction = data.function.replaceAll('<<quote>>', '"');
var result = eval(rawFunction);
if(data.key) {
var message = {}
message[data.key] = result
var messageString = JSON.stringify(message);
event.source.postMessage(messageString , '*');
}
}
} catch (e) { }
}, false);
window.onresize = function () {
player.setSize(window.innerWidth, window.innerHeight);
};
function sendPlatformMessage(message) {
switch(platform) {
case 'android':
<<playerId>>.postMessage(message);
break;
case 'ios':
<<playerId>>.postMessage(message, '*');
break;
case 'web':
window.parent.postMessage(message, '*');
break;
}
}
function sendMessage(key, data) {
var message = {};
message[key] = data;
message['playerId'] = '<<playerId>>';
var messageString = JSON.stringify(message);
sendPlatformMessage(messageString);
}
function getVideoData() {
return prepareDataForPlatform(player.getVideoData());
}
function getPlaylist() {
return prepareDataForPlatform(player.getPlaylist());
}
function getAvailablePlaybackRates(){
return prepareDataForPlatform(player.getAvailablePlaybackRates());
}
function prepareDataForPlatform(data) {
if(platform == 'android') return data;
return JSON.stringify(data);
}
function handleFullScreenForMobilePlatform() {
if(platform != 'web') {
var ytFrame = document.getElementsByTagName('iframe')[0].contentWindow.document;
var fsButton = ytFrame.getElementsByClassName('ytp-fullscreen-button ytp-button')[0];
if(fsButton != null) {
var fsButtonCopy = fsButton.cloneNode(true);
fsButton.replaceWith(fsButtonCopy);
fsButtonCopy.onclick = function() {
sendMessage('FullscreenButtonPressed', '');
};
}
}
}
</script>
</body>
</html>
NOW VERY IMP NOTE : You may ask why do _controller.webViewController.runJavaScript('window.focus();); if i am injecting a listener in html dom? Because the listener will be there but the YouTube player will steal the DOM focus and listner wont listen anything unless the focus is released back.
I havent tried the in flutter keyboard listener after this fix so do post results if anyone tries that.
Upvotes: 0
Reputation: 7921
Seem's like focus()
only works on buttons (tested on document.body
and regular <p>
elements, both do nothing).
Fixed CODEPEN (old, see update below!)
Note that I added document.getElementById("btn").focus()
before document.body.focus()
, yet the focus switches to btn
and not document.body
. The browser I'm using is Chrome 41.0.2272.118 on my Surface Pro 3 (Win 8.1)
Turns out, if the button is hidden using display:none
or visibility:hidden
, the focus()
method doesn't work anymore! This made me suspect that the issue was actually with whether or not an element is focusable or not. Apparently a tabindex
of -1
makes an element focusable, so I tested this by setting document.body.tabIndex = -1;
before calling document.body.focus()
. and lo and behold, IT WORKS!
Upvotes: 3