woojoo666
woojoo666

Reputation: 7921

Can't Change Focus from Youtube IFrame

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:

CODEPEN

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

Answers (2)

Darsh Singh
Darsh Singh

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

woojoo666
woojoo666

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)

UPDATE

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!

updated CODEPEN

Upvotes: 3

Related Questions