Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] How Autofocus? Keyboard like arrows and 'f' don't work unless I click on the video first #1033

Open
1 task done
sgehrman opened this issue Dec 10, 2024 · 2 comments
Assignees

Comments

@sgehrman
Copy link
Contributor

Is there an existing issue for this?

  • I have searched the existing issues

Package

youtube_player_iframe (Default)

What happened?

keyboard shortcuts don't work unless video is clicked

What is the expected behaviour?

should be autofocusable.

How to reproduce?

use it

Flutter Doctor Output

duh
@sgehrman
Copy link
Contributor Author

I'm on Web, and have autoplay turned on, so it plays without first clicking the widget, but it doesn't have focus so keyboard events don't work.

@darshsingh116
Copy link

FINALLY CREATED A SOLUTION FOR FOCUS ISSUE!!!!

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants