yuliskov / MediaServiceCore

Unofficial Java api for YouTube
37 stars 27 forks source link

DIAL with lounge API #10

Open iBicha opened 5 months ago

iBicha commented 5 months ago

Hey! I'm the author of Playlet, a YouTube app for Roku TV.

I recently embarked on the journey to support casting from the YouTube app using lounge.

Some projects, like https://github.com/henriquekano/youtube-lounge-api, https://github.com/yuliskov/MediaServiceCore, and (especially) https://github.com/patrickkfkan/yt-cast-receiver helped me understand the protocol better, so I wanted to say thanks, and also share a bit of my findings in case it is useful.

As I tried to understand the DIAL spec, which is the "Link using Wifi" option, I found it was mostly working like this:

Chain of events

  1. Devices (mobile) broadcast an SSDP message M-SEARCH

    M-SEARCH * HTTP/1.1
    HOST: 239.255.255.250:1900
    MAN: "ssdp:discover"
    MX: 5
    ST: urn:dial-multiscreen-org:service:dial:1
  2. And devices (TVs) capable of accepting casting would respond with something like

    Cache-Control: max-age=3600
    ST: urn:dial-multiscreen-org:service:dial:1
    USN: ID::urn:dial-multiscreen-org:service:dial:1
    Ext: 
    Server: Roku/12.5.5 UPnP/1.0 Roku/12.5.5
    LOCATION: http://IP:8060/dial/dd.xml
    WAKEUP: MAC=MAC;Timeout=10

    Notice the LOCATION header. This would be a link to an xml file containing the device description.

  3. The device (mobile) makes an http GET request to the LOCATION. My TV's response looks something like this

    <?xml version="1.0"?>
    <root xmlns="urn:schemas-upnp-org:device-1-0">
    <specVersion>
        <major>1</major>
        <minor>0</minor>
    </specVersion>
    <device>
        <deviceType>urn:roku-com:device:player:1-0</deviceType>
        <friendlyName>TV</friendlyName>
        <manufacturer>XXX</manufacturer>
        <manufacturerURL>xxx.com</manufacturerURL>
        <modelDescription>Roku Streaming Player Network Media</modelDescription>
        <modelName>XXX</modelName>
        <modelNumber>XXX</modelNumber>
        <modelURL>http://www.roku.com/</modelURL>
        <serialNumber>XXXXXXX</serialNumber>
        <UDN>uuid:XXXXXXXXXX</UDN>
        <iconList>
            <icon>
                <mimetype>image/png</mimetype>
                <width>360</width>
                <height>219</height>
                <depth>8</depth>
                <url>device-image.png</url>
            </icon>
        </iconList>
        <serviceList>
            <service>
                <serviceType>urn:roku-com:service:ecp:1</serviceType>
                <serviceId>urn:roku-com:serviceId:ecp1-0</serviceId>
                <controlURL></controlURL>
                <eventSubURL></eventSubURL>
                <SCPDURL>ecp_SCPD.xml</SCPDURL>
            </service>
            <service>
                <serviceType>urn:dial-multiscreen-org:service:dial:1</serviceType>
                <serviceId>urn:dial-multiscreen-org:serviceId:dial1-0</serviceId>
                <controlURL></controlURL>
                <eventSubURL></eventSubURL>
                <SCPDURL>dial_SCPD.xml</SCPDURL>
            </service>
        </serviceList>
    </device>
    </root>

    Notice how the service list contains urn:dial-multiscreen-org:service:dial:1, which is saying the device is capable of DIAL. Good. In the response headers for hitting the device description http://IP:8060/dial/dd.xml, there's one important header to capture:

    Application-Url: http://IP:8060/dial

    Because it is the URL used to check if a specific DIAL compatible app is installed or not.

  4. The device (mobile) makes a GET request to Application-Url + /YouTube (case sensitive). In this case that would be http://IP:8060/dial/YouTube. The response would be something like this:

    <?xml version="1.0" encoding="UTF-8" ?>
    <service
    xmlns="urn:dial-multiscreen-org:schemas:dial" dialVer="2.1">
    <name>YouTube</name>
    <options allowStop="true" />
    <state>stopped</state>
    </service>

    If the mobile device gets a valid response, then it will see the device over wifi and offer to connect to it (it will show up as the friendlyName from the device description file.) Note: on my Roku, the request will be blocked, unless the Origin header is set to https://www.youtube.com

  5. The response from http://IP:8060/dial/YouTube Might be in different state, and the mobile devices react differently based on the presence of the additionalData field.

    • In the case the xml does not contain additionalData (so similar to the content from step 4)
    • The mobile device would send a POST request to http://IP:8060/dial/YouTube, which includes theme and pairingCode url encoded in the body. The pairing code can be registered using https://www.youtube.com/api/lounge/pairing/register_pairing_code and then the mobile phone would join the lounge once the code is registered.
    • In the case the xml contains additionalData, e.g. :
      Example copied from https://github.com/henriquekano/youtube-lounge-api/wiki/Discovery
      <service xmlns="urn:dial-multiscreen-org:schemas:dial">
      <name>YouTube</name>
      <options allowStop="false"/>
      <state>running</state>
      <link rel="run" href="http://192.168.x.y:58722//run"/>
      <additionalData>
      <brand>Sony</brand>
      <model>PS4 Pro</model>
      <screenId>qweqweqwe</screenId>
      <theme>??</theme>
      <deviceId>ssssssssss</deviceId>
      <loungeToken>xxxxxxxx</loungeToken>
      <loungeTokenRefreshIntervalMs>1500000</loungeTokenRefreshIntervalMs>
      </additionalData>
      </service>

Then the phone will not send a pairing code, and will just join the lounge, since it has everything it needs already.

  1. One last thing, when <options allowStop="true" /> is returned, the mobile device (actually I tested this with Chrome browser) will a send a DELETE request to close the app. Just a small detail.

Implementation

So the device is responsible for responding to the M-SEARCH requests, and for providing a device description xml file. So by definition, only one app can have the identifier YouTube for DIAL. This is why when the YouTube mobile app casts to a device, the official YouTube tv app will launch. This is why I went a different route.

  1. A DIAL server I have a udp connection listening for broadcast messages. When it sees an M-SEARCH, it responds just like the device does (see step 2 in the first section), but I set the LOCATION here to point to a file that I'm serving on a local web server running on the app.

  2. Serving device description Using a local server, I'm serving a "virtual device description" xml file, and also serve an xml file for the/dial/YouTube file. For the device description, I set the friendlyName to "Playlet on" + tvName so that users can Identify the app they want to cast to.

So far I'm have a working implementation (just a PoC that needs cleanup) here https://github.com/iBicha/playlet/pull/276 that does what I'm describing. I thought the approach could be used to add DIAL support to SmartTube. Most people never link using TV code, since it is more convenient for the auto discovery over WIFI to just work.

That's it. I just wanted to overshare since information about this stuff is pretty scarce, and it's hard to find.

Cheers!

iBicha commented 5 months ago

One last thing I want to mention, is that Roku platform is pretty limited, and that's why I've gone through this route. I haven't worked on Android TV, but there might be better ways to do things.

yuliskov commented 5 months ago

I'm glad that I have helped. Interesting info, friend. Thanks for sharing.

Regards, Yuriy

On Sat, Feb 10, 2024, 18:45 Brahim Hadriche @.***> wrote:

Hey! I'm the author of Playlet https://github.com/iBicha/playlet, a YouTube app for Roku TV.

I recently embarked on the journey to support casting from the YouTube app using lounge.

Some projects, like https://github.com/henriquekano/youtube-lounge-api, https://github.com/yuliskov/MediaServiceCore, and (especially) https://github.com/patrickkfkan/yt-cast-receiver helped me understand the protocol better, so I wanted to say thanks, and also share a bit of my findings in case it is useful.

As I tried to understand the DIAL spec, which is the "Link using Wifi" option, I found it was mostly working like this: Chain of events

  1. Devices (mobile) broadcast an SSDP https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol message M-SEARCH

M-SEARCH * HTTP/1.1 HOST: 239.255.255.250:1900 MAN: "ssdp:discover" MX: 5 ST: urn:dial-multiscreen-org:service:dial:1

  1. And devices (TVs) capable of accepting casting would respond with something like

Cache-Control: max-age=3600 ST: urn:dial-multiscreen-org:service:dial:1 USN: ID::urn:dial-multiscreen-org:service:dial:1 Ext: Server: Roku/12.5.5 UPnP/1.0 Roku/12.5.5 LOCATION: http://IP:8060/dial/dd.xml WAKEUP: MAC=MAC;Timeout=10

Notice the LOCATION header. This would be a link to an xml file containing the device description.

  1. The device (mobile) makes an http GET request to the LOCATION. My TV's response looks something like this

<?xml version="1.0"?>

1 0 urn:roku-com:device:player:1-0 TV XXX xxx.com Roku Streaming Player Network Media XXX XXX http://www.roku.com/ XXXXXXX uuid:XXXXXXXXXX image/png 360 219 8 device-image.png urn:roku-com:service:ecp:1 urn:roku-com:serviceId:ecp1-0 ecp_SCPD.xml urn:dial-multiscreen-org:service:dial:1 urn:dial-multiscreen-org:serviceId:dial1-0 dial_SCPD.xml

Notice how the service list contains urn:dial-multiscreen-org:service:dial:1, which is saying the device is capable of DIAL. Good. In the response headers for hitting the device description http://IP:8060/dial/dd.xml, there's one important header to capture:

Application-Url: http://IP:8060/dial

Because it is the URL used to check if a specific DIAL compatible app is installed or not.

  1. The device (mobile) makes a GET request to Application-Url + /YouTube (case sensitive). In this case that would be http://IP:8060/dial/YouTube. The response would be something like this:

<?xml version="1.0" encoding="UTF-8" ?> <service xmlns="urn:dial-multiscreen-org:schemas:dial" dialVer="2.1">

YouTube stopped

If the mobile device gets a valid response, then it will see the device over wifi and offer to connect to it (it will show up as the friendlyName from the device description file.) Note: on my Roku, the request will be blocked, unless the Origin header is set to https://www.youtube.com

  1. The response from http://IP:8060/dial/YouTube Might be in different state, and the mobile devices react differently based on the presence of the additionalData field.

    • In the case the xml does not contain additionalData (so similar to the content from step 4)
    • In the case the xml contains additionalData, e.g. :

Example copied from https://github.com/henriquekano/youtube-lounge-api/wiki/Discovery

YouTube running Sony PS4 Pro qweqweqwe ?? ssssssssss xxxxxxxx 1500000

Then the phone will not send a pairing code, and will just join the lounge, since it has everything it needs already.

  • In the case the xml contains additionalData, but not containing a loungeToken, e.g :

<?xml version="1.0" encoding="UTF-8" ?> <service xmlns="urn:dial-multiscreen-org:schemas:dial" dialVer="2.1">

YouTube stopped xxxxxxx XXX XXXX XXXXXXXXXXXXXX XXXXXXXXXXX

Now this is the part I haven't explored yet - but I'm guessing a passiveSessionId is some form of new feature allowing the session to be passive until a device connects to it.

  1. One last thing, when is returned, the mobile device (actually I tested this with Chrome browser) will a send a DELETE request to close the app. Just a small detail.

Implementation

So the device is responsible for responding to the M-SEARCH requests, and for providing a device description xml file. So by definition, only one app can have the identifier YouTube for DIAL. This is why when the YouTube mobile app casts to a device, the official YouTube tv app will launch. This is why I went a different route.

1.

A DIAL server I have a udp connection listening for broadcast messages. When it sees an M-SEARCH, it responds just like the device does (see step 2 in the first section), but I set the LOCATION here to point to a file that I'm serving on a local web server running on the app. 2.

Serving device description Using a local server, I'm service a "virtual device description" xml file, and also serve an xml file for the/dial/YouTube file. For the device description, I set the friendlyName to "Playlet on" + tvName so that users can Identify the app they want to cast to.

So far I'm have a working implementation (just a PoC that needs cleanup) here iBicha/playlet#276 https://github.com/iBicha/playlet/pull/276 that does what I'm describing. I thought the approach could be used to add DIAL support to SmartTube. Most people never link using TV code, since it is more convenient for the auto discovery over WIFI to just work.

That's it. I just wanted to overshare since information about this stuff is pretty scarce, and it's hard to find.

Cheers!

— Reply to this email directly, view it on GitHub https://github.com/yuliskov/MediaServiceCore/issues/10, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABM7Z6D65WCHHE7ZI5ODVT3YS6P3JAVCNFSM6AAAAABDC2WTWOVHI2DSMVQWIX3LMV43ASLTON2WKOZSGEZDQNRQGY4TSOI . You are receiving this because you are subscribed to this thread.Message ID: @.***>