diff --git a/data/www/js/cast-url.js b/data/www/js/cast-url.js new file mode 100644 index 0000000..d203b19 --- /dev/null +++ b/data/www/js/cast-url.js @@ -0,0 +1,74 @@ +var applicationID = '81585E3E'; +var namespace = 'urn:x-cast:com.jrk.obscreen'; +var session = null; + +if (!chrome.cast || !chrome.cast.isAvailable) { + setTimeout(initializeCastApi, 1000); +} + +function initializeCastApi() { + var sessionRequest = new chrome.cast.SessionRequest(applicationID); + var apiConfig = new chrome.cast.ApiConfig(sessionRequest, + sessionListener, + receiverListener); + + chrome.cast.initialize(apiConfig, onInitSuccess, onError); +} + +function onInitSuccess() { + // console.log('onInitSuccess'); +} + +function onError(message) { + console.error('onError: ' + JSON.stringify(message)); +} + +function onSuccess(message) { + // console.log('onSuccess: ' + JSON.stringify(message)); +} + +// function onStopAppSuccess() { +// console.log('onStopAppSuccess'); +// } + +function sessionListener(e) { + console.log('New session ID: ' + e.sessionId); + session = e; + session.addUpdateListener(sessionUpdateListener); +} + +function sessionUpdateListener(isAlive) { + console.log((isAlive ? 'Session Updated' : 'Session Removed') + ': ' + session.sessionId); + if (!isAlive) { + session = null; + } +} + +function receiverListener(e) { + // Due to API changes just ignore this. +} + +function sendMessage(message) { + if (session != null) { + session.sendMessage(namespace, message, onSuccess.bind(this, message), onError); + } else { + chrome.cast.requestSession(function (e) { + session = e; + sessionListener(e); + session.sendMessage(namespace, message, onSuccess.bind(this, message), onError); + }, onError); + } +} + +// function stopApp() { +// session.stop(onStopAppSuccess, onError); +// } + +jQuery(function ($) { + $(document).on('click', '.cast-url', function () { + sendMessage({ + type: 'load', + url: $('#' + $(this).attr('data-target-id')).val() + }); + }); +}); \ No newline at end of file diff --git a/data/www/js/lib/cast-sender.js b/data/www/js/lib/cast-sender.js new file mode 100644 index 0000000..b46c3ad --- /dev/null +++ b/data/www/js/lib/cast-sender.js @@ -0,0 +1,13 @@ + (function(){/* + + Copyright The Closure Library Authors. + SPDX-License-Identifier: Apache-2.0 + */ + 'use strict';var l=function(){var a=h,b=0;return function(){return b"}else d=void 0===c?"undefined":null===c?"null":typeof c;w("Argument is not a %s (or a non-Element, non-Location mock); got: %s","HTMLScriptElement",d)}a instanceof A&&a.constructor===A?d=a.g:(d=typeof a,w("expected object of type TrustedResourceUrl, got '"+a+"' of type "+("object"!= + d?d:a?Array.isArray(a)?"array":d:"null")),d="type_error:TrustedResourceUrl");c.src=d;(d=c.ownerDocument&&c.ownerDocument.defaultView)&&d!=m?d=q(d.document):(null===p&&(p=q(m.document)),d=p);d&&c.setAttribute("nonce",d);(document.head||document.documentElement).appendChild(c)},I=function(){var a=B(),b=[];if(1' + device.friendly_name + '')); + // } + // } + // }); + // }); - $(document).on('click', '.cast-scan', function () { - showModal('modal-playlist-cast-scan'); - const $modal = $('.modal-playlist-cast-scan:visible'); - const $holder = $modal.find('.cast-devices'); - const $loading = $modal.find('.loading'); - - $loading.removeClass('hidden'); - $holder.removeClass('hidden'); - $holder.html(''); - $loading.html($loading.attr('data-loading')); - - $.ajax({ - method: 'GET', - url: route_cast_scan, - headers: {'Content-Type': 'application/json'}, - success: function (response) { - $loading.addClass('hidden') - - for (let i = 0; i < response.devices.length; i++) { - const device = response.devices[i]; - $holder.append($('
  • ' + device.friendly_name + '
  • ')); - } - } - }); - }); - - $(document).on('click', '.cast-device', function () { - const $modal = $('.modal-playlist-cast-scan:visible'); - const $holder = $modal.find('.cast-devices'); - const $loading = $modal.find('.loading'); - - $holder.addClass('hidden'); - $loading.removeClass('hidden'); - $loading.html($loading.attr('data-casting')); - - const id = $(this).attr('data-id'); - - $.ajax({ - url: route_cast_url, - method: 'POST', - data: JSON.stringify({ - device: id, - url: $('#playlist-preview-url').val() - }), - headers: {'Content-Type': 'application/json'}, - success: function (response) { - $loading.addClass('hidden'); - hideModal(); - }, - error: function () { - $loading.addClass('hidden'); - $holder.removeClass('hidden'); - } - }); - }); + // $(document).on('click', '.cast-device', function () { + // const $modal = $('.modal-playlist-cast-scan:visible'); + // const $holder = $modal.find('.cast-devices'); + // const $loading = $modal.find('.loading'); + // + // $holder.addClass('hidden'); + // $loading.removeClass('hidden'); + // $loading.html($loading.attr('data-casting')); + // + // const id = $(this).attr('data-id'); + // + // $.ajax({ + // url: route_cast_url, + // method: 'POST', + // data: JSON.stringify({ + // device: id, + // url: $('#playlist-preview-url').val() + // }), + // headers: {'Content-Type': 'application/json'}, + // success: function (response) { + // $loading.addClass('hidden'); + // hideModal(); + // }, + // error: function () { + // $loading.addClass('hidden'); + // $holder.removeClass('hidden'); + // } + // }); + // }); main(); }); diff --git a/data/www/scss/pages/_playlist.scss b/data/www/scss/pages/_playlist.scss index 8842060..db5f2fd 100644 --- a/data/www/scss/pages/_playlist.scss +++ b/data/www/scss/pages/_playlist.scss @@ -11,75 +11,75 @@ align-self: stretch; color: $gscale6; } - - .modal-playlist-cast-scan { - h2 { - text-align: left; - } - - .alert { - padding: 10px; - font-size: 12px; - margin-bottom: 20px; - display: block; - text-align: center; - - i { - margin-right: 5px; - } - - a { - margin: 0; - } - } - - .loading { - color: $gscaleF; - animation-duration: 2s; - animation-iteration-count: infinite; - animation-name: blinkfade; - } - - ul.cast-devices { - list-style: none; - margin: 0; - padding: 0; - - li { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - list-style: none; - border-bottom: 1px solid $gscale2; - border-radius: $baseRadius; - - a { - flex: 1; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - padding: 20px 15px; - color: $gscaleF; - align-self: stretch; - - i { - - margin-right: 10px; - } - } - - &:hover { - background: $gscale2; - } - } - - li:last-child { - border: none; - } - } - } + // + //.modal-playlist-cast-scan { + // h2 { + // text-align: left; + // } + // + // .alert { + // padding: 10px; + // font-size: 12px; + // margin-bottom: 20px; + // display: block; + // text-align: center; + // + // i { + // margin-right: 5px; + // } + // + // a { + // margin: 0; + // } + // } + // + // .loading { + // color: $gscaleF; + // animation-duration: 2s; + // animation-iteration-count: infinite; + // animation-name: blinkfade; + // } + // + // ul.cast-devices { + // list-style: none; + // margin: 0; + // padding: 0; + // + // li { + // display: flex; + // flex-direction: row; + // justify-content: flex-start; + // align-items: center; + // list-style: none; + // border-bottom: 1px solid $gscale2; + // border-radius: $baseRadius; + // + // a { + // flex: 1; + // display: flex; + // flex-direction: row; + // justify-content: flex-start; + // align-items: center; + // padding: 20px 15px; + // color: $gscaleF; + // align-self: stretch; + // + // i { + // + // margin-right: 10px; + // } + // } + // + // &:hover { + // background: $gscale2; + // } + // } + // + // li:last-child { + // border: none; + // } + // } + //} .modal-slide { h2 { diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 0000000..ccb4430 --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,42 @@ +user nginx; +worker_processes 4; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /var/log/nginx/access.log; + sendfile on; + keepalive_timeout 65; + client_max_body_size 200G; + + autoindex on; + + server { + root /var/www/html/public; + listen 80 default_server; + listen 443 ssl default_server; + + ssl_certificate /ssl/ssl-cert-snakeoil.pem; + ssl_certificate_key /ssl/ssl-cert-snakeoil.key; + + location / { + proxy_connect_timeout 60; + proxy_read_timeout 60; + proxy_send_timeout 60; + proxy_intercept_errors on; + proxy_http_version 1.1; + proxy_pass http://localhost:5000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 51fec59..35696ad 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,8 @@ flask==2.3.3 flask-restx==1.3.0 -pychromecast==13.1.0 python-dotenv cron-descriptor waitress flask-login pysqlite3 psutil -zeroconf \ No newline at end of file diff --git a/src/controller/CoreController.py b/src/controller/CoreController.py index 002448e..5cf4aa0 100644 --- a/src/controller/CoreController.py +++ b/src/controller/CoreController.py @@ -10,8 +10,8 @@ class CoreController(ObController): def register(self): self._app.add_url_rule('/manifest.json', 'manifest', self.manifest, methods=['GET']) self._app.add_url_rule('/favicon.ico', 'favicon', self.favicon, methods=['GET']) - self._app.add_url_rule('/cast-scan', 'cast_scan', self.cast_scan, methods=['GET']) - self._app.add_url_rule('/cast-url', 'cast_url', self.cast_url, methods=['POST']) + # self._app.add_url_rule('/cast-scan', 'cast_scan', self.cast_scan, methods=['GET']) + # self._app.add_url_rule('/cast-url', 'cast_url', self.cast_url, methods=['POST']) def manifest(self): with open("{}/manifest.jinja.json".format(self.get_template_dir()), 'r') as file: @@ -24,14 +24,14 @@ class CoreController(ObController): def favicon(self): return send_file("{}/favicon.ico".format(self.get_web_dir()), mimetype='image/x-icon') - def cast_scan(self): - return jsonify({ - 'devices': fetch_friendly_names(discovery_timeout=5) - }) - - def cast_url(self): - data = request.get_json() - success = cast_url(friendly_name=data.get('device'), url=data.get('url'), discovery_timeout=5) + # def cast_scan(self): + # return jsonify({ + # 'devices': fetch_friendly_names(discovery_timeout=5) + # }) + # + # def cast_url(self): + # data = request.get_json() + # success = cast_url(friendly_name=data.get('device'), url=data.get('url'), discovery_timeout=5) return jsonify({ 'success': success diff --git a/src/util/UtilChromecast.py b/src/util/UtilChromecast.py index 72acfac..e6c254a 100644 --- a/src/util/UtilChromecast.py +++ b/src/util/UtilChromecast.py @@ -1,69 +1,69 @@ -import time -import pychromecast -import zeroconf -import logging - -from pychromecast.discovery import stop_discovery -from pychromecast import CastBrowser, SimpleCastListener, get_chromecast_from_host, Chromecast -from pychromecast.controllers import BaseController -from typing import Optional - - -APPLICATION_ID = '81585E3E' - - -class CastController(BaseController): - def __init__(self): - super(CastController, self).__init__("urn:x-cast:com.jrk.obscreen") - - def load_url(self, url: str): - self.send_message({ - 'url': url, - 'type': 'load' - }) - - -def _discover(discovery_timeout: int = 5): - zconf = zeroconf.Zeroconf() - browser = pychromecast.CastBrowser(pychromecast.SimpleCastListener(), zconf) - browser.start_discovery() - time.sleep(discovery_timeout) - stop_discovery(browser) - - return browser - - -def fetch_friendly_names(discovery_timeout: int = 5): - return [{"friendly_name": cast_info.friendly_name} for device, cast_info in _discover(discovery_timeout).devices.items()] - - -def fetch_chromecast(friendly_name: str, discovery_timeout: int = 5) -> Optional[Chromecast]: - for uuid, cast_info in _discover(discovery_timeout).devices.items(): - if cast_info.friendly_name == friendly_name: - try: - return get_chromecast_from_host((cast_info.host, cast_info.port, uuid, cast_info.model_name, cast_info.friendly_name)) - except: - pass - - logging.info("No chromecast found for friendly_name {}".format(friendly_name)) - return None - - -def cast_url(friendly_name: str, url: str, discovery_timeout: int = 5) -> bool: - chromecast = fetch_chromecast(friendly_name, discovery_timeout) - - if not chromecast: - logging.info("Can't instantiate Chromecast {}".format(friendly_name)) - return False - - chromecast.wait() - chromecast.quit_app() - time.sleep(2) - - cast_controller = CastController() - chromecast.register_handler(cast_controller) - chromecast.start_app(APPLICATION_ID) - time.sleep(2) - cast_controller.load_url(url) - - return True +# import time +# import pychromecast +# import zeroconf +# import logging +# +# from pychromecast.discovery import stop_discovery +# from pychromecast import CastBrowser, SimpleCastListener, get_chromecast_from_host, Chromecast +# from pychromecast.controllers import BaseController +# from typing import Optional +# +# +# APPLICATION_ID = '81585E3E' +# +# +# class CastController(BaseController): +# def __init__(self): +# super(CastController, self).__init__("urn:x-cast:com.jrk.obscreen") +# +# def load_url(self, url: str): +# self.send_message({ +# 'url': url, +# 'type': 'load' +# }) +# +# +# def _discover(discovery_timeout: int = 5): +# zconf = zeroconf.Zeroconf() +# browser = pychromecast.CastBrowser(pychromecast.SimpleCastListener(), zconf) +# browser.start_discovery() +# time.sleep(discovery_timeout) +# stop_discovery(browser) +# +# return browser +# +# +# def fetch_friendly_names(discovery_timeout: int = 5): +# return [{"friendly_name": cast_info.friendly_name} for device, cast_info in _discover(discovery_timeout).devices.items()] +# +# +# def fetch_chromecast(friendly_name: str, discovery_timeout: int = 5) -> Optional[Chromecast]: +# for uuid, cast_info in _discover(discovery_timeout).devices.items(): +# if cast_info.friendly_name == friendly_name: +# try: +# return get_chromecast_from_host((cast_info.host, cast_info.port, uuid, cast_info.model_name, cast_info.friendly_name)) +# except: +# pass +# +# logging.info("No chromecast found for friendly_name {}".format(friendly_name)) +# return None +# +# +# def cast_url(friendly_name: str, url: str, discovery_timeout: int = 5) -> bool: +# chromecast = fetch_chromecast(friendly_name, discovery_timeout) +# +# if not chromecast: +# logging.info("Can't instantiate Chromecast {}".format(friendly_name)) +# return False +# +# chromecast.wait() +# chromecast.quit_app() +# time.sleep(2) +# +# cast_controller = CastController() +# chromecast.register_handler(cast_controller) +# chromecast.start_app(APPLICATION_ID) +# time.sleep(2) +# cast_controller.load_url(url) +# +# return True diff --git a/views/playlist/component/edit.jinja.html b/views/playlist/component/edit.jinja.html index ca4cf09..f77591e 100644 --- a/views/playlist/component/edit.jinja.html +++ b/views/playlist/component/edit.jinja.html @@ -103,9 +103,12 @@ - +{# #} diff --git a/views/playlist/list.jinja.html b/views/playlist/list.jinja.html index 1bc5b3f..e3e84cb 100644 --- a/views/playlist/list.jinja.html +++ b/views/playlist/list.jinja.html @@ -46,6 +46,21 @@ } var contents = {{ json_dumps(contents) | safe }} + @@ -129,7 +144,7 @@
    {% include 'playlist/modal/add.jinja.html' %} - {% include 'playlist/modal/cast-scan.jinja.html' %} +{# {% include 'playlist/modal/cast-scan.jinja.html' %}#} {% with is_notification=True %}{% include 'slideshow/slides/modal/edit.jinja.html' %}{% endwith %} {% with is_notification=False %}{% include 'slideshow/slides/modal/edit.jinja.html' %}{% endwith %}