core: Roku Media player does not stream to "Play On Roku"

The problem

When trying to stream content to Roku through the media player on HA, the Play on Roku app starts but exits immediately. In a packet capture, I see the correct HTTP request being sent to the Play on Roku app, however this request dies on the Roku side. This issue does not appear to be limited to the Home Assistant integration, as I have tried sending the POST directly from my local computer with the same result. I have tried connecting to Roku on port 8080 and 8085 but couldn’t get any debugs logs or traces, which I believe is mostly due to my lack of knowledge about the Roku platform.

Also tried the chrome extension RokuKast which has the same problem even for videos from the internet, and reading the comment on the RokuKast repo it looks like the endpoint might have changed: https://github.com/dgreuel/RokuKast/issues/38

Everything works OK when I use the custom component and the side-loaded app as outlined here, but it’s missing some key features that I would like to use: https://github.com/lvcabral/ha-roku-media-player

I realise this might not be a true HA issue but I’m posting it here as this appears to be the most active community related to the topic. Would appreciate any help with this. Thank you!

What version of Home Assistant Core has the issue?

2022.11.4

What was the last working version of Home Assistant Core?

No response

What type of installation are you running?

Home Assistant Container

Integration causing the issue

roku

Link to integration documentation on our website

https://www.home-assistant.io/integrations/roku/

Diagnostics information

config_entry-roku-fe39abfab7f8393cc8cbd8d7209d4768.json.txt

Example YAML snippet

No response

Anything in the logs that might be useful for us?

No response

Additional information

Roku Software Version: 11.5.0 build 4235-88 This is the current format of the POST request in the packet capture and looks correct to me: http://10.76.103.17:8060/input/15985?t=v&u=https%3A%2F%2Fhomeassistant.local.s1ngh.ca%2Fmedia%2Flocal%2FBigBuckBunny.mp4%3FauthSig%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5NWFkZmYzMjM3YWM0YjllODY5ZTJiNDc4NzIxMWZiOCIsInBhdGgiOiIvbWVkaWEvbG9jYWwvQmlnQnVja0J1bm55Lm1wNCIsInBhcmFtcyI6e30sImlhdCI6MTY3MDgzMzA2MiwiZXhwIjoxNjcwOTE5NDYyfQ.s2dDURHQMm3kTwbKCJaTPlnCbrS4IOeBsUm7sva31xg&videoName=media-source%3A%2F%2Fmedia_source%2Flocal%2FBigBuckBunny.mp4&videoFormat=mp4

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 2
  • Comments: 22 (7 by maintainers)

Most upvoted comments

@attain-squiggly-zeppelin nice I’ll see about adapting the node code over the upcoming holiday breaks

It appears that Roku disabled Play On Roku. Read about if from a Roku employee here

@ctalkington

I did a capture of the android app talking to my Roku Ultra.

It uses a protocol it says ecp-2. It seems similar to regular ecp but over web sockets.

However, upon connecting the roku device sends a challenge with a nonce, and the app responds with a sha1 of the concatenation of the nonce and a hard coded uuid.

The hard coded uuid is F3A278B8-1C6F-44A9-9D89-F1979CA4C6F1.

Example exchange:

GET http://192.168.1.176:8060/ecp-session HTTP/1.1
Sec-WebSocket-Origin: Android
Sec-WebSocket-Protocol: ecp-2
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: jU9KP0hjtBw7zbNQUH+SFw==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate
Host: 192.168.1.176:8060
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0-roku1


2023-11-11 18:45:13.491
{
    "notify": "authenticate",
    "param-challenge": "x4F6nRyCvRjhe1a+L2QZBw==",
    "timestamp": "233317.714"
}
2023-11-11 18:45:13.537
{
    "param-response": "XzpAJnctaMSVBr1DjvWWLUU2Vaw=",
    "request": "authenticate",
    "request-id": "0"
}
2023-11-11 18:45:13.561
{
    "response": "authenticate",
    "response-id": "0",
    "status": "200",
    "status-msg": "OK"
}

Turning on my tv via roku:

{"request":"key-press","request-id":"6","param-key":"Power"}
{"response":"key-press","response-id":"6","status":"200","status-msg":"OK"}

Streaming a video from my phone:

{"request":"input","request-id":"8","param-params":"{\"a\":\"sta\",\"t\":\"v\",\"u\":\"http:\\\/\\\/192.168.1.160:5150\\\/VIDEO%2Fm_0.m3u8\",\"framerate\":\"30\",\"h\":\"192.168.1.160:5150\",\"videoname\":\"20231104_111237\",\"k\":\"http:\\\/\\\/192.168.1.160:5150\\\/%2FVIDEO_THUMB%2F17870\",\"videoformat\":\"hls\",\"videoresolution\":\"1080\"}","param-channel-id":"15985"}

Proof of concept nodejs code: (Sorry, it would have taken me longer to write it in Python. It should be simple enough to convert to Python for someone who uses it every day.)

index.mjs:

import WebSocket from 'ws';
import crypto from 'crypto';

const key = 'F3A278B8-1C6F-44A9-9D89-F1979CA4C6F1';

const host = process.argv[2];
console.log('connecting to ', host);

const ws = new WebSocket(`ws://${host}:8060/ecp-session`, 'ecp-2');

ws.on('error', console.error);

ws.on('open', function open() {
  console.log('opened!');
});

ws.on('close', function () {
  console.log('closed!');
});

const dummy_video = {"a":"sta","t":"v","u":"http://192.168.1.160:5150/VIDEO%2Fm_0.m3u8","framerate":"30","h":"192.168.1.160:5150","videoname":"20231104_111237","k":"http://192.168.1.160:5150/%2FVIDEO_THUMB%2F17870","videoformat":"hls","videoresolution":"1080"};


let request_counter = 0;

ws.on('message', function message(data) {
  console.log('received: %s', data);
  const msg = JSON.parse(data);
  if (msg.notify === 'authenticate') {
    const hasher = crypto.createHash('sha1');
    hasher.update(msg['param-challenge'] + key);
    const response = {
      "param-response": hasher.digest('base64'),
      "request": "authenticate",
      'request-id': (request_counter++).toString()
    };
    console.log(JSON.stringify(response));
    ws.send(JSON.stringify(response));
  }

  if (msg.response === 'authenticate') {
    if (msg.status !== '200') throw new Error('failed to authenticate!');
    ws.send(JSON.stringify({
      "request": "input",
      "request-id": (request_counter++).toString(),
      "param-params": JSON.stringify(dummy_video),
      "param-channel-id":"15985"
    }));
  }
});

The package.json:

{
  "name": "roku-play-stream",
  "version": "1.0.0",
  "main": "index.mjs",
  "license": "MIT",
  "dependencies": {
    "ws": "^8.14.2"
  }
}

Usage: yarn install node ./index.mjs 192.168.1.100 assuming your Roku is on that one dot one hundred address.

Hope this helps!

so the issue with HA or community registered app is the process to get such approved by Roku. They are unlikely to allow something that’s only purpose is to bypass a security change they made so the app would need to provide some other functionality and implement deep linking of media as a feature.

I had started a discussion about such a community venture of getting https://github.com/lvcabral/ha-roku-cast-app as an official app but it would take a large effort so sideloading and supporting custom app id for deep linking in HA integration would likely be easier to achieve. but this was during the holidays and my personal availability has been low since. I’ll try to get to the rokuecp and HA updates in the next few weeks.