Skip to content

Instantly share code, notes, and snippets.

@mzyy94
Last active August 2, 2021 19:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mzyy94/aa25615639fd3466d37bac7da204778e to your computer and use it in GitHub Desktop.
Save mzyy94/aa25615639fd3466d37bac7da204778e to your computer and use it in GitHub Desktop.
Discover DIAL device and request DIAL REST API
#!/usr/bin/env python3
from urllib.request import *
import xml.etree.ElementTree as ET
headers = {'Origin': 'package:dial.py'}
class Dial:
appUrl = ''
appName = ''
ns = {'role': 'urn:dial-multiscreen-org:schemas:dial'}
def __init__(self, appUrl, appName):
self.appUrl = appUrl
self.appName = appName
def getInfo(self):
url = self.appUrl + self.appName
req = Request(url=url, headers=headers, method='GET')
data = {'url': req.full_url}
try:
with urlopen(req) as res:
body = res.read().decode()
service = ET.fromstring(body)
data['name'] = service.find('./role:name', self.ns).text
data['state'] = service.find('./role:state', self.ns).text
data['allowStop'] = service.find('./role:options[@allowStop]', self.ns).attrib['allowStop']
data['resourceName'] = service.find("./role:link[@rel='run']", self.ns).attrib['href']
except:
pass
return data
def launch(self):
info = self.getInfo()
if info['state'] == 'running':
return None
url = info['url']
req = Request(url=url, headers=headers, method='POST')
try:
with urlopen(req):
return True
except:
return False
def stop(self):
info = self.getInfo()
if info['state'] != 'running':
return None
url = info['url'] + '/' + info['resourceName']
req = Request(url=url, headers=headers, method='DELETE')
try:
with urlopen(req):
return True
except:
return False
def hide(self):
info = self.getInfo()
if info['state'] != 'running':
return None
url = info['url'] + '/' + info['resourceName'] + '/hide'
req = Request(url=url, headers=headers, method='POST')
try:
with urlopen(req):
return True
except:
return False
def main():
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument('action', help='Action (state/launch/stop/hide)')
parser.add_argument('-u', '--url', required=True, help='Application URL')
parser.add_argument('-n', '--name', required=True, help='Application Name')
args = parser.parse_args()
ret = True
dial = Dial(args.url, args.name)
if args.action == 'state':
info = dial.getInfo()
print(info['state'])
elif args.action == 'launch':
ret = dial.launch()
elif args.action == 'stop':
ret = dial.stop()
elif args.action == 'hide':
ret = dial.hide()
else:
print('Invalid action: %s' % args.action)
parser.print_help()
ret = False
sys.exit(1 - int(ret or '0'))
if __name__ == '__main__':
# e.g. python3 dial.py -u http://192.168.181.222:56789/apps/ -n YouTube launch
main()
#!/usr/bin/env python3
import socket
from random import randrange
body = """
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 1
ST: urn:dial-multiscreen-org:service:dial:1
""".replace('\n', '\r\n').encode('utf-8')
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(5)
s.bind((socket.gethostname(), randrange(50000, 60000)))
s.sendto(body, ('239.255.255.250', 1900))
while True:
try:
buf, _ = s.recvfrom(1024)
print(buf.decode('utf-8'))
except:
break
s.close()
globalThis.cast = globalThis.cast || {};
const nop = () => {};
const errorLog = (text) => (err) => console.log(text, err);
const launchApp = function (dialAppName = "AmazonInstantVideo") {
const sessionRequest = new chrome.cast.SessionRequest(
chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
);
sessionRequest.dialRequest = new chrome.cast.DialRequest(dialAppName);
chrome.cast.requestSession(
(session) => (cast.session = session),
errorLog("requestSession failed"),
sessionRequest
);
};
const stopApp = function () {
if (cast.session) {
cast.session.stop(nop, errorLog("stop failed"));
const status = document.getElementById("dial-status");
if (status) {
status.innerText = "Disconnected.";
}
}
};
initializeCastApiLegacy = function () {
const sessionRequest = new chrome.cast.SessionRequest(
chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
);
const apiConfig = new chrome.cast.ApiConfig(sessionRequest, nop, nop);
apiConfig.customDialLaunchCallback = async (dialInfo) => {
console.log("dialCallback", dialInfo.receiver.friendlyName, dialInfo);
const status = document.getElementById("dial-status");
if (status) {
status.innerText = `Connected: ${dialInfo.receiver.friendlyName}[${dialInfo.receiver.ipAddress}]`;
}
};
chrome.cast.initialize(
apiConfig,
() => console.log("Initialized."),
errorLog("initialize failed")
);
};
window["__onGCastApiAvailable"] = function (loaded, _errorInfo) {
if (loaded) {
initializeCastApiLegacy();
}
};
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>LaunchDial</title>
<script src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
<script src="launch_dial.js"></script>
</head>
<body>
<button onclick="launchApp('AmazonInstantVideo')">Launch Prime Video</button>
<button onclick="stopApp()">Stop</button>
<div id="dial-status">Waiting to connect.</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment