renative icon indicating copy to clipboard operation
renative copied to clipboard

Bind keys to TV remote

Open redgoalsuk opened this issue 4 years ago • 16 comments

Hi

What is the process to bind keys on the TV remote to control the video player - eg play/pause/seek using renative?

https://video-react.js.org/components/player/

thanks

import React, { Component } from 'react';
import Prism from 'prismjs'
import { PrismCode } from 'react-prism';
import { Player, ControlBar } from 'video-react';
import { Button } from 'reactstrap';

const sources = {
  sintelTrailer: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
  bunnyTrailer: 'http://media.w3.org/2010/05/bunny/trailer.mp4',
  bunnyMovie: 'http://media.w3.org/2010/05/bunny/movie.mp4',
  test: 'http://media.w3.org/2010/05/video/movie_300.webm'
};

export default class VideoPlayer extends Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      source: sources.bunnyMovie
    };

    this.play = this.play.bind(this);
    this.pause = this.pause.bind(this);
    this.load = this.load.bind(this);
    this.changeCurrentTime = this.changeCurrentTime.bind(this);
    this.seek = this.seek.bind(this);
    this.changePlaybackRateRate = this.changePlaybackRateRate.bind(this);
    this.changeVolume = this.changeVolume.bind(this);
    this.setMuted = this.setMuted.bind(this);
  }

  componentDidMount() {
    // subscribe state change
    this.player.subscribeToStateChange(this.handleStateChange.bind(this));
  }

  setMuted(muted) {
    return () => {
      this.player.muted = muted;
    };
  }

  handleStateChange(state) {
    // copy player state to this component's state
    this.setState({
      player: state
    });
  }

  play() {
    this.player.play();
  }

  pause() {
    this.player.pause();
  }

  load() {
    this.player.load();
  }

  changeCurrentTime(seconds) {
    return () => {
      const { player } = this.player.getState();
      this.player.seek(player.currentTime + seconds);
    };
  }

  seek(seconds) {
    return () => {
      this.player.seek(seconds);
    };
  }

  changePlaybackRateRate(steps) {
    return () => {
      const { player } = this.player.getState();
      this.player.playbackRate = player.playbackRate + steps;
    };
  }

  changeVolume(steps) {
    return () => {
      const { player } = this.player.getState();
      this.player.volume = player.volume + steps;
    };
  }

  changeSource(name) {
    return () => {
      this.setState({
        source: sources[name]
      });
      this.player.load();
    };
  }

  render() {
    return (
      <div>
        <Player
          ref={player => {
            this.player = player;
          }}
          autoPlay
        >
          <source src={this.state.source} />
          <ControlBar autoHide={false} />
        </Player>
        <div className="py-3">
          <Button onClick={this.play} className="mr-3">
            play()
          </Button>
          <Button onClick={this.pause} className="mr-3">
            pause()
          </Button>
          <Button onClick={this.load} className="mr-3">
            load()
          </Button>
        </div>
        <div className="pb-3">
          <Button onClick={this.changeCurrentTime(10)} className="mr-3">
            currentTime += 10
          </Button>
          <Button onClick={this.changeCurrentTime(-10)} className="mr-3">
            currentTime -= 10
          </Button>
          <Button onClick={this.seek(50)} className="mr-3">
            currentTime = 50
          </Button>
        </div>
        <div className="pb-3">
          <Button onClick={this.changePlaybackRateRate(1)} className="mr-3">
            playbackRate++
          </Button>
          <Button onClick={this.changePlaybackRateRate(-1)} className="mr-3">
            playbackRate--
          </Button>
          <Button onClick={this.changePlaybackRateRate(0.1)} className="mr-3">
            playbackRate+=0.1
          </Button>
          <Button onClick={this.changePlaybackRateRate(-0.1)} className="mr-3">
            playbackRate-=0.1
          </Button>
        </div>
        <div className="pb-3">
          <Button onClick={this.changeVolume(0.1)} className="mr-3">
            volume+=0.1
          </Button>
          <Button onClick={this.changeVolume(-0.1)} className="mr-3">
            volume-=0.1
          </Button>
          <Button onClick={this.setMuted(true)} className="mr-3">
            muted=true
          </Button>
          <Button onClick={this.setMuted(false)} className="mr-3">
            muted=false
          </Button>
        </div>
        <div className="pb-3">
          <Button onClick={this.changeSource('sintelTrailer')} className="mr-3">
            Sintel teaser
          </Button>
          <Button onClick={this.changeSource('bunnyTrailer')} className="mr-3">
            Bunny trailer
          </Button>
          <Button onClick={this.changeSource('bunnyMovie')} className="mr-3">
            Bunny movie
          </Button>
          <Button onClick={this.changeSource('test')} className="mr-3">
            Test movie
          </Button>
        </div>
        <div>State</div>
        <pre>
          <PrismCode className="language-json">
            {JSON.stringify(this.state.player, null, 2)}
          </PrismCode>
        </pre>
      </div>
    );
  }
}

redgoalsuk avatar Apr 25 '20 13:04 redgoalsuk

Ok I created a simple page to try and see if I can access the tizen API and it seems tizen variable is undefined which means I cannot access the binding functionality via the renative wrapper. This is my simple code

import React, { Component } from "react";
import { StyleSheet, Text, View } from "react-native";

export default class App extends Component {

  constructor(props, context) {
    super(props, context);

    this.keyCode = "Initial";

  }

  componentDidMount() {

    window.addEventListener('keydown', this.onKeyPress);
    document.body.addEventListener('keydown', this.onKeyPress);

    if (typeof tizen !== 'undefined')
    {
      var value = tizen.tvinputdevice.getSupportedKeys();
      this.keyCode = value
    }
    else {
      this.keyCode = "tizen undefined"
    }

  }

  render() {
    return (<div>
      {this.keyCode}
      </div>
      );
  }
}

I run this via

rnv run -p tizen -d -t 192.168.1.31

and output is "tizen undefined"

this is the wrapper code that is generated in platformBuilds/blank_tizen when installing the App to the Tizen TV, it seems ok to me?

<html>

<head>
  <meta charset="UTF-8">
  <title>App Shell</title>
  <style>
    body {
      background-color: white;
    }
  </style>
</head>

<body style="margin:0px;padding:0px;overflow:hidden">
  <iframe style="margin:0px;padding:0px;overflow:hidden" id="iframe" height="100%" width="100%" frameborder="0"
    scrolling="no" allowfullscreen="true" sandbox="allow-scripts allow-same-origin">
  </iframe>
  <script>
    var href = 'http://192.168.1.67:8087';

    setTimeout( function () {
      var iframe = document.getElementById( 'iframe' );
      iframe.src = href
      iframe.onload = function () {
        if ( typeof tizen !== "undefined" ) iframe.contentWindow.tizen = tizen;
        if ( typeof webapis !== "undefined" ) iframe.contentWindow.webapis = webapis;
        if ( typeof webOS !== "undefined" ) iframe.contentWindow.webOS = webOS;
        if ( typeof webOSDev !== "undefined" ) iframe.contentWindow.webOSDev = webOSDev;
      }
    }, 5000 );
  </script>
</body>

</html>

You can see tizen variable is being passed to the iframe so it should be available in the App code but it is not there? Also the generated config.xml does contain

<tizen:privilege name='http://tizen.org/privilege/tv.inputdevice' />

So we should be able to access the tizen API

RNV = 0.29.0 Node = 14.0.0 NPM = 6.14.4

If I create a new project in Tizen Studio and put in the following code

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <link rel="stylesheet" type="text/css" href="css/style.css"/>
    <script src="./main.js"></script>
</head>
<body>

<script>
var value = tizen.tvinputdevice.getSupportedKeys();
console.log(value); 

</script>
</body>
</html>

And launch this as a Debug Tizen web application then I can see in the console the output which looks fine?

Screenshot from 2020-04-28 06-01-29

redgoalsuk avatar Apr 28 '20 13:04 redgoalsuk

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Nov 14 '20 15:11 stale[bot]

In case this may be of interest to anyone in the future - the behaviour above is caused by the fact that 'tizen' is set with a 5s Timeout (by the way - can anyone explain why?) - it means that it is not available directly at launch, only slightly later...

MalgoBlock avatar Dec 08 '20 11:12 MalgoBlock

The best solution would probably be to ditch the iframe and instead let the appShell be the result from webpack-dev-server (but scripts/assets loaded via external URL prefix). I think this could solve all issues and make renative more future proof.

Also there is this, but this only solves the issue for Tizen, not potential future issues with WebOS and such.

EyMaddis avatar Dec 08 '20 15:12 EyMaddis

To me the same timeout problem was happening, but 100ms is enough in my case (I'm making a simple "Exit the App?" dialog screen)

chmiiller avatar Jan 09 '21 20:01 chmiiller

I have the same problem, but I try to set the tizen after 5 seconds and it doesn't work for me!

Where do you add this code?

guuhgalvao avatar May 18 '21 21:05 guuhgalvao

does it help to set the listeners on document 'load' event?

CHaNGeTe avatar May 19 '21 06:05 CHaNGeTe

Could you give me an example?

guuhgalvao avatar May 19 '21 15:05 guuhgalvao

window.addEventListener('load', (event) => {
  // check if tizen global exists here
});

CHaNGeTe avatar May 19 '21 15:05 CHaNGeTe

Should I put this code in a useEffect? And in the ./src/app/index.web.js file?

guuhgalvao avatar May 19 '21 15:05 guuhgalvao

yeap, that's what I did:

useEffect(() => {
    setTimeout(() => {
        window.addEventListener('keydown', onKeyDownMenu);
    }, 100);
}, []);

const onKeyDownMenu = (event) => {
    switch (event.keyCode) {
        case 8: //backspace
        case 10009:
            break;
    }
}

And it doesn't have to be on index.web.js, it could be at any component

chmiiller avatar May 19 '21 18:05 chmiiller

This does not work?

useEffect(() => {
    window.addEventListener('load', (event) => {
        console.log('page is fully loaded, use tizen object here');
        var value = tizen.tvinputdevice.getSupportedKeys();
        console.log(value); 
    });
}, []);

CHaNGeTe avatar May 20 '21 06:05 CHaNGeTe

no, it doesn't work

this is my code, the load event is not executed:

useEffect(() => {
        window.addEventListener('load', (event) => {
            console.log('page is fully loaded, use tizen object here');

            setTimeout(() => {
                if ( typeof tizen !== "undefined" ) {
                    var value = tizen.tvinputdevice.getSupportedKeys();
                    console.log(value);
                }
            }, 6000);
        });
}, []);

this code is on the first screen of the app

guuhgalvao avatar May 20 '21 13:05 guuhgalvao

Couple of observations from my experience - in case it may help someone: First note - the key registration only works on actual device, not on the simulator. Second note - what I have noticed is that there is a different behaviour when app is served locally in the development mode and when it's installed in the production mode - in my case it's because of the way it's being served. In my case the Key registration works only in production mode and then it works without problems. I simply check for 'tizen' in window and then run tizen.tvinputdevice.registerKey(<name>)

MalgoBlock avatar May 26 '21 06:05 MalgoBlock

I'm also facing the same problem, tizen in window is undefined. I tested in a real device as well.

You guys found any solution to detect remote events?

Could you check if this works?

const handler = e => { if ( e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "Enter" || e.key === "Select" || e.key === "Back" ) console.log("e",e); };

useEffect(() => { window.addEventListener("keydown", handler); return () => { window.removeEventListener("keydown", handler); }; }, []);

For inputs, I'm using the prop "onKeyPress" to listen events.

Thanks in advance

JoaoCEAlmeida avatar Jun 23 '21 10:06 JoaoCEAlmeida

Was there any progress made on this? I am experiencing the same issue with the tizen variable in development mode.

crewtaylorfd avatar Feb 17 '22 21:02 crewtaylorfd