Embed: chromecast

This commit is contained in:
Cat 2023-12-13 16:29:24 +01:00 committed by Thulinma
parent a3521a992d
commit 564de0ef12
8 changed files with 435 additions and 14 deletions

File diff suppressed because one or more lines are too long

View file

@ -99,9 +99,17 @@ svg.icon.timeout{display:inline-block;height:1em;width:1em;margin:0;margin-right
.mistvideo-videobackground *{position:absolute;filter:opacity(0);transition:filter 0s 2s;width:100%;height:100%} .mistvideo-videobackground *{position:absolute;filter:opacity(0);transition:filter 0s 2s;width:100%;height:100%}
.mistvideo-videobackground [data-front]{z-index:1;filter:opacity(1);transition:filter 2s} .mistvideo-videobackground [data-front]{z-index:1;filter:opacity(1);transition:filter 2s}
.mistvideo-videocontainer{position:relative;overflow:hidden} .mistvideo-videocontainer{position:relative;overflow:hidden}
.mistvideo.casting .mistvideo-controls{bottom:0}
.mistvideo.casting .mistvideo-video{filter:blur(1em)}
.mistvideo.casting .mistvideo-maincontainer{overflow:hidden}
.mistvideo.casting .mistvideo-casting{position:absolute;top:50%;transform:translateY(-50%);text-align:center;width:100%;font-size:2em;text-shadow:1px 1px 5px #000a}
.mistvideo-error[data-passive]{bottom:auto;left:auto;height:auto;margin:.5em;padding:.5em} .mistvideo-error[data-passive]{bottom:auto;left:auto;height:auto;margin:.5em;padding:.5em}
.mistvideo-error[data-passive] .message{max-width:none} .mistvideo-error[data-passive] .message{max-width:none}
.mistvideo-error .mistvideo-buttoncontainer{display:flex;flex-flow:row nowrap;justify-content:center} .mistvideo-error .mistvideo-buttoncontainer{display:flex;flex-flow:row nowrap;justify-content:center}
.mistvideo-error .mistvideo-buttoncontainer .mistvideo-button{white-space:nowrap} .mistvideo-error .mistvideo-buttoncontainer .mistvideo-button{white-space:nowrap}
.browser-ie .mist.icon.loading{animation:mistvideo-spin 1.5s infinite linear;transform-origin:50% 50%} .browser-ie .mist.icon.loading{animation:mistvideo-spin 1.5s infinite linear;transform-origin:50% 50%}
.browser-ie .mist.icon.loading .spin{animation:none} .browser-ie .mist.icon.loading .spin{animation:none}
.mistvideo-chromecast{display:flex}
google-cast-launcher{width:24px;height:24px;--connected-color:$fill;--disconnected-color:$semiFill}
google-cast-launcher.active{--connected-color:$accent;--disconnected-color:$fill}
.mistvideo.casting .mistvideo-slideshow_mode{display:none}

View file

@ -99,12 +99,20 @@ svg.icon.timeout{display:inline-block;height:1em;width:1em;margin:0;margin-right
.mistvideo-videobackground *{position:absolute;filter:opacity(0);transition:filter 0s 2s;width:100%;height:100%} .mistvideo-videobackground *{position:absolute;filter:opacity(0);transition:filter 0s 2s;width:100%;height:100%}
.mistvideo-videobackground [data-front]{z-index:1;filter:opacity(1);transition:filter 2s} .mistvideo-videobackground [data-front]{z-index:1;filter:opacity(1);transition:filter 2s}
.mistvideo-videocontainer{position:relative;overflow:hidden} .mistvideo-videocontainer{position:relative;overflow:hidden}
.mistvideo.casting .mistvideo-controls{bottom:0}
.mistvideo.casting .mistvideo-video{filter:blur(1em)}
.mistvideo.casting .mistvideo-maincontainer{overflow:hidden}
.mistvideo.casting .mistvideo-casting{position:absolute;top:50%;transform:translateY(-50%);text-align:center;width:100%;font-size:2em;text-shadow:1px 1px 5px #000a}
.mistvideo-error[data-passive]{bottom:auto;left:auto;height:auto;margin:.5em;padding:.5em} .mistvideo-error[data-passive]{bottom:auto;left:auto;height:auto;margin:.5em;padding:.5em}
.mistvideo-error[data-passive] .message{max-width:none} .mistvideo-error[data-passive] .message{max-width:none}
.mistvideo-error .mistvideo-buttoncontainer{display:flex;flex-flow:row nowrap;justify-content:center} .mistvideo-error .mistvideo-buttoncontainer{display:flex;flex-flow:row nowrap;justify-content:center}
.mistvideo-error .mistvideo-buttoncontainer .mistvideo-button{white-space:nowrap} .mistvideo-error .mistvideo-buttoncontainer .mistvideo-button{white-space:nowrap}
.browser-ie .mist.icon.loading{animation:mistvideo-spin 1.5s infinite linear;transform-origin:50% 50%} .browser-ie .mist.icon.loading{animation:mistvideo-spin 1.5s infinite linear;transform-origin:50% 50%}
.browser-ie .mist.icon.loading .spin{animation:none} .browser-ie .mist.icon.loading .spin{animation:none}
.mistvideo-chromecast{display:flex}
google-cast-launcher{width:24px;height:24px;--connected-color:$fill;--disconnected-color:$semiFill}
google-cast-launcher.active{--connected-color:$accent;--disconnected-color:$fill}
.mistvideo.casting .mistvideo-slideshow_mode{display:none}
.mistvideo-log{margin:.5em 0} .mistvideo-log{margin:.5em 0}
.mistvideo-log .logs{max-height:10em;min-height:5em;width:100%;padding:.2em 0;padding-right:1em;overflow-y:auto;overflow-x:hidden;font-size:.9em} .mistvideo-log .logs{max-height:10em;min-height:5em;width:100%;padding:.2em 0;padding-right:1em;overflow-y:auto;overflow-x:hidden;font-size:.9em}
.mistvideo-log .logs table td{vertical-align:top;padding:0} .mistvideo-log .logs table td{vertical-align:top;padding:0}
@ -130,3 +138,4 @@ svg.icon.graph{position:absolute;right:0;top:0;bottom:0;width:6em}
select{border-radius:0} select{border-radius:0}
input[type=checkbox]{margin:0;margin-right:.2em;border:1px solid $semiFill;border-radius:0;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;width:.8em;height:.8em;color:inherit;position:relative;cursor:pointer} input[type=checkbox]{margin:0;margin-right:.2em;border:1px solid $semiFill;border-radius:0;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;width:.8em;height:.8em;color:inherit;position:relative;cursor:pointer}
input[type=checkbox]:checked:after{content:"\2713";position:absolute;bottom:-.2em;left:0;font-size:1.2em} input[type=checkbox]:checked:after{content:"\2713";position:absolute;bottom:-.2em;left:0;font-size:1.2em}
.mistvideo.casting .mistvideo-displayCombo,.mistvideo.casting .mistvideo-forcePlayer,.mistvideo.casting .mistvideo-forceType{display:none!important}

View file

@ -927,6 +927,10 @@ function MistVideo(streamName,options) {
]; ];
for (var i in events) { for (var i in events) {
MistUtil.event.addListener(MistVideo.video,events[i],function(e){ MistUtil.event.addListener(MistVideo.video,events[i],function(e){
if (e.message && (e.message == "chromecast")) {
//if the event originates from the chromecast, it is already printed by the chromecast's log passthrough
return;
}
MistVideo.log("Player event fired: "+e.type); MistVideo.log("Player event fired: "+e.type);
}); });
} }
@ -1301,7 +1305,7 @@ function MistVideo(streamName,options) {
if (MistVideo.destroyed) { return; } if (MistVideo.destroyed) { return; }
onStreamInfo(JSON.parse(d)); onStreamInfo(JSON.parse(d));
},function(xhr){ },function(xhr){
var msg = "Connection failed: the media server may be offline"; var msg = "Connection failed: the media server at "+MistVideo.options.host+" may be offline";
MistVideo.showError(msg,{reload:30}); MistVideo.showError(msg,{reload:30});
if (!MistVideo.info) { if (!MistVideo.info) {
MistUtil.event.send("initializeFailed",null,options.target); MistUtil.event.send("initializeFailed",null,options.target);

View file

@ -12,6 +12,7 @@ MistSkins["default"] = {
classes: ["mistvideo"], classes: ["mistvideo"],
children: [{ children: [{
type: "hoverWindow", type: "hoverWindow",
classes: ["mistvideo-maincontainer"],
mode: "pos", mode: "pos",
style: {position: "relative"}, style: {position: "relative"},
transition: { transition: {
@ -129,6 +130,9 @@ MistSkins["default"] = {
then: { then: {
type: "container", type: "container",
children: [{ children: [{
type: "chromecast",
classes: ["mistvideo-pointer"]
},{
type: "loop", type: "loop",
classes: ["mistvideo-pointer"] classes: ["mistvideo-pointer"]
}, },
@ -195,6 +199,9 @@ MistSkins["default"] = {
type: "container", type: "container",
classes: ["mistvideo-center"], classes: ["mistvideo-center"],
children: [{ children: [{
type: "chromecast",
classes: ["mistvideo-pointer"]
},{
type: "loop", type: "loop",
classes: ["mistvideo-pointer"] classes: ["mistvideo-pointer"]
}, },
@ -412,7 +419,7 @@ MistSkins["default"] = {
//remove all the things when unmuted //remove all the things when unmuted
var fu = function(){ var fu = function(){
if (!MistVideo.video.muted) { if (!MistVideo.player.api.muted) {
if (largeMutedButton.parentNode) { if (largeMutedButton.parentNode) {
MistVideo.container.removeChild(largeMutedButton); MistVideo.container.removeChild(largeMutedButton);
} }
@ -1366,9 +1373,8 @@ MistSkins["default"] = {
var button = this.skin.icons.build("loop"); var button = this.skin.icons.build("loop");
var video = this.video; var video = this.video;
var api = this.player.api;
button.set = function(){ button.set = function(){
if (api.loop) { if (MistVideo.player.api.loop) {
MistUtil.class.remove(this,"off"); MistUtil.class.remove(this,"off");
} }
else { else {
@ -1377,7 +1383,7 @@ MistSkins["default"] = {
}; };
MistUtil.event.addListener(button,"click",function(e){ MistUtil.event.addListener(button,"click",function(e){
api.loop = !api.loop; MistVideo.player.api.loop = !MistVideo.player.api.loop;
this.set(); this.set();
}); });
button.set(); button.set();
@ -1880,7 +1886,7 @@ MistSkins["default"] = {
var events = ["waiting","seeking","stalled"]; var events = ["waiting","seeking","stalled"];
for (var i in events) { for (var i in events) {
MistUtil.event.addListener(MistVideo.video,events[i],function(e){ MistUtil.event.addListener(MistVideo.video,events[i],function(e){
if ((!this.paused) && ("container" in MistVideo)) { if ((MistVideo.player.api && !MistVideo.player.api.paused) && ("container" in MistVideo)) {
addIcon(e); addIcon(e);
} }
},icon); },icon);
@ -1915,7 +1921,7 @@ MistSkins["default"] = {
if (!options.polling && !options.passive && !options.hideTitle) { if (!options.polling && !options.passive && !options.hideTitle) {
var header = document.createElement("h3"); var header = document.createElement("h3");
message_container.appendChild(header); message_container.appendChild(header);
header.appendChild(document.createTextNode("The player has encountered a problem")); header.appendChild(document.createTextNode("The "+(MistVideo.casting ? "chromecast" : "player")+" has encountered a problem"));
} }
var p = document.createElement("p"); var p = document.createElement("p");
@ -2052,7 +2058,10 @@ MistSkins["default"] = {
since = (new Date()).getTime(); since = (new Date()).getTime();
var event = this.log(message,"error"); var event;
if (!MistVideo.casting) {
event = this.log(message,"error");
}
var message_container = container.message(message,false,options); var message_container = container.message(message,false,options);
message_global = message_container; message_global = message_container;
@ -2061,7 +2070,19 @@ MistSkins["default"] = {
message_container.appendChild(button_container); message_container.appendChild(button_container);
MistUtil.empty(button_container); MistUtil.empty(button_container);
if (options.softReload) { if (MistVideo.casting && !options.passive) {
var obj = {
type: "button",
label: "Stop casting",
onclick: function(){
MistVideo.detachFromCast();
}
};
if (!isNaN(options.softReload+"")) { obj.delay = options.softReload; }
button_container.appendChild(MistVideo.UI.buildStructure(obj));
}
if (options.softReload && !MistVideo.casting) {
var obj = { var obj = {
type: "button", type: "button",
label: "Reload video", label: "Reload video",
@ -2116,7 +2137,7 @@ MistSkins["default"] = {
MistVideo.container.removeAttribute("data-loading"); MistVideo.container.removeAttribute("data-loading");
} }
if (event.defaultPrevented) { if (event && event.defaultPrevented) {
MistVideo.log("Error event was defaultPrevented, not showing."); MistVideo.log("Error event was defaultPrevented, not showing.");
container.clear(); container.clear();
} }
@ -2376,6 +2397,342 @@ MistSkins["default"] = {
} }
return container; return container;
},
chromecast: function(){
var MistVideo = this;
if ((!"".indexOf) || (MistVideo.options.host.indexOf("localhost") > -1) || (MistVideo.options.host.indexOf("::1") > -1)) { return; } //it's old IE or the Mist host is localhost and so it won't work ^_^
var ele = document.createElement("div");
var casting_ele = document.createElement("div");
casting_ele.className = "mistvideo-casting";
var api, reload, nextCombo, unload;
var selectedTracks = {};
var remoteData = {
currentTime: false,
paused: true,
volume: 1,
muted: false,
buffer: [],
loop: (MistVideo.player.api ? MistVideo.player.api.loop : MistVideo.options.loop)
};
MistVideo.casting = false;
function onCastLoad(tries) {
if (!window.chrome || !window.chrome.cast || !window.chrome.cast.isAvailable || (tries > 5)) {
if (ele.parentNode) { ele.parentNode.removeChild(ele); }
MistVideo.log("Chromecast is not supported");
console.warn(chrome,chrome.cast,chrome.cast ? chrome.cast.isAvailable : undefined,cast);
return;
}
if (!window.cast) {
if (!tries) { tries = 0; }
MistVideo.log("Casting api loaded but cast function not yet available, retrying..");
MistVideo.timers.start(function(){
onCastLoad(tries++);
},200)
return;
}
MistVideo.log("Chromecast API loaded");
if (!window.loadedCastApi || (window.loadedCastApi == "loading")) {
window.loadedCastApi = true; //so we don't try this multiple times
}
var cast_ele = document.createElement("google-cast-launcher");
ele.appendChild(cast_ele);
cast.framework.CastContext.getInstance().setOptions({
receiverApplicationId: "E5F1558C",
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
});
function detachFromCast(keepSession) {
MistUtil.class.remove(cast_ele,"active");
MistUtil.class.remove(MistVideo.container,"casting");
if (casting_ele.parentNode) { casting_ele.parentNode.removeChild(casting_ele) }
if (!keepSession && (cast.framework.CastContext.getInstance().getCurrentSession())) {
cast.framework.CastContext.getInstance().getCurrentSession().endSession(true);
}
if (api) {
MistVideo.player.api = api;
api.currentTime = remoteData.currentTime;
api.play();
MistVideo.reload = reload;
MistVideo.nextCombo = nextCombo;
MistVideo.unload = unload;
}
else {
MistVideo.player.api.play();
}
if (MistVideo.player.api && MistVideo.player.api.setTracks && MistUtil.object.keys(selectedTracks).length) {
MistVideo.player.api.setTracks(selectedTracks);
}
MistVideo.casting = false;
MistVideo.log("Detached chromecast session");
}
MistVideo.detachFromCast = detachFromCast;
cast_ele.addEventListener("click",function(e){
e.stopPropagation();
//if (false) {
if (MistUtil.class.has(cast_ele,"active")) {
detachFromCast();
}
else {
MistUtil.class.add(cast_ele,"active");
casting_ele.innerHTML = "Select a device to cast to";
MistVideo.container.appendChild(casting_ele);
MistUtil.class.add(MistVideo.container,"casting");
MistVideo.log("chromecast: pausing player")
MistVideo.player.api.pause();
MistVideo.container.setAttribute("data-loading","waiting for cast");
MistVideo.casting = true;
if (!window.MistCast) {
window.MistCast = {
send: function(obj){
cast.framework.CastContext.getInstance().getCurrentSession().sendMessage("urn:x-cast:mistcaster",JSON.stringify(obj));
}
};
}
function loadStream() {
cast.framework.CastContext.getInstance().getCurrentSession().addMessageListener("urn:x-cast:mistcaster",function(ns,message){
if (MistVideo.destroyed) { detachFromCast(); }
message = JSON.parse(message);
//console.log("chromecast message received:",message);
if (message.type) {
switch(message.type){
case "log":
case "error": {
MistVideo.log("[Chromecast] "+message.message,message.type);
break;
}
case "showError":{
MistVideo.showError.apply(MistVideo,message.args);
break;
}
case "event": {
switch (message.event) {
case "timeupdate": {
remoteData.currentTime = message.currentTime;
MistUtil.event.send(message.event,"chromecast",MistVideo.video);
break;
}
case "progress": {
remoteData.buffer = message.buffer;
MistUtil.event.send(message.event,"chromecast",MistVideo.video);
break;
}
case "pause":
case "paused":
case "ended":
case "play":
case "playing": {
remoteData.paused = message.paused;
MistUtil.event.send(message.event,"chromecast",MistVideo.video);
break;
}
case "volumechange": {
remoteData.volume = message.volume;
remoteData.muted = message.muted;
MistUtil.event.send(message.event,"chromecast",MistVideo.video);
break;
}
default: {
MistUtil.event.send(message.event,"chromecast",MistVideo.video);
}
}
break;
}
case "detach": {
if (message.n == MistVideo.n) {
detachFromCast(true); //don't murder the session
}
break;
}
default: {
console.log("Unknown chromecast message type",message);
}
}
}
});
var d = {
type: "load",
n: MistVideo.n, //indentify which player instance we are
options: {
host: MistVideo.options.host,
loop: MistVideo.options.loop, //will be overwritten with the current value if there is a player api
poster: MistVideo.options.poster, //should be an absolute url, because the location will be different
streaminfo: MistVideo.options.streaminfo,
urlappend: MistVideo.options.urlappend,
forcePriority: MistVideo.options.forcePriority,
setTracks: MistVideo.options.setTracks, //when the track selection is changed through the UI, the selected track is saved in the options, so this passes on the currently enforced tracks
controls: false,
skin: "default" //TODO: right now the skin can't really be transfered because there are functions in there that won't be in the JSON. At some point we should fix this, probably by having the Mist backend include a custom skin definition with the player code.
},
stream: MistVideo.stream
};
if (MistVideo.info && (MistVideo.info.type != "live")) {
d.time = MistVideo.player.api.currentTime;
}
if (MistVideo.options.skin == "dev") {
d.options.skin = MistVideo.options.skin;
}
if (MistVideo.player && MistVideo.player.api) {
d.volume = MistVideo.player.api.volume;
d.muted = MistVideo.player.api.muted;
d.options.loop = MistVideo.player.api.loop;
}
MistCast.send(d);
api = MistVideo.player.api;
reload = MistVideo.reload;
nextCombo = MistVideo.nextCombo;
unload = MistVideo.unload;
selectedTracks = MistVideo.options.setTracks ? MistVideo.options.setTracks : {};
MistVideo.player.api = new Proxy(api,{
get: function(target,key,receiver){
var property = target[key];
switch (key) {
case "muted":
case "volume":
case "currentTime":
case "paused":
case "loop": {
return remoteData[key];
}
case "buffered": {
return new function(){
this.length = remoteData.buffer.length;
this.start = function(i){
return remoteData.buffer[i][0];
}
this.end = function(i){
return remoteData.buffer[i][1];
}
}
}
case "setTracks":
case "play":
case "pause": {
return function() {
//put the arguments into an array so JSON doesn't turn it into an object
var a = [];
for (var i = 0; i < arguments.length; i++) {
a.push(arguments[i]);
}
if (key == "setTracks") {
//save the selected tracks so that we can restore them when casting detaches
MistUtil.object.extend(selectedTracks,arguments[0]);
}
MistCast.send({type: "cmd", cmd: key, args: a});
}
}
}
return property;
},
set: function(target,key,value){
if (key == "loop") {
remoteData[key] = value; //just assume chromecast will apply it properly
}
//console.log("set",key,value);
MistCast.send({type:"set",cmd:key,value:value});
}
});
api.pause();
MistVideo.reload = function(){
MistCast.send({type:"reload"});
};
MistVideo.nextCombo = function(){
MistCast.send({type:"nextCombo"});
};
MistVideo.unload = function(){
//this one should actually unload the current player, but detach the chromecast session first
detachFromCast();
return unload.apply(this,arguments);
};
var d = cast.framework.CastContext.getInstance().getCurrentSession().getCastDevice();
if (d && d.friendlyName) { casting_ele.innerHTML = "Casting to "+d.friendlyName; }
var on_session_end = function(){
if (cast.framework.CastContext.getInstance().getSessionState() == "SESSION_ENDED") {
if (MistUtil.class.has(cast_ele,"active")) {
detachFromCast();
}
cast.framework.CastContext.getInstance().removeEventListener(cast.framework.CastContextEventType.SESSION_STATE_CHANGED,on_session_end);
}
};
cast.framework.CastContext.getInstance().addEventListener(cast.framework.CastContextEventType.SESSION_STATE_CHANGED,on_session_end);
/*for (let i of ["sessionstatechanged","caststatechanged"]) {
cast.framework.CastContext.getInstance().addEventListener(i,function(){
console.warn(i,cast.framework.CastContext.getInstance().getSessionState());
});
}*/
MistVideo.log("Attached chromecast session");
}
if (cast.framework.CastContext.getInstance().getCurrentSession()) {
loadStream();
}
else {
cast.framework.CastContext.getInstance().requestSession().then(function(){
//console.log("Session requested");
if (!cast.framework.CastContext.getInstance().getCurrentSession()) { throw "Could not connect to the cast device"; }
loadStream();
},function(e){
MistVideo.log("Chromecast session ended: "+e);
detachFromCast();
});
}
}
},true); //capture so that chrome's own handler doesn't fire
}
if ((!window.chrome || !window.chrome.cast) && (!window.loadedCastApi)) {
window['__onGCastApiAvailable'] = function(loaded, errorInfo) {
if (!loaded) {
MistVideo.log("Error while loading chromecast API: "+errorInfo);
}
onCastLoad();
};
window.loadedCastApi = "loading";
var script = document.createElement("script");
script.setAttribute("src","//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1");
document.head.appendChild(script);
MistVideo.log("Appending chromecast script");
}
else {
if (window.loadedCastApi == "loading") {
MistVideo.log("Not appending chromecast script - still loading");
MistVideo.timers.start(function(){
onCastLoad();
},200);
}
else {
MistVideo.log("Not appending chromecast script - already loaded");
onCastLoad();
}
}
return ele;
} }
}, },
colors: { colors: {
@ -2868,7 +3225,7 @@ MistSkins.dev.structure.submenu.children.unshift({
}, },
then: { then: {
type: "container", type: "container",
classes: ["mistvideo-description"], classes: ["mistvideo-description","mistvideo-displayCombo"],
style: { display: "block" }, style: { display: "block" },
children: [ children: [
{type: "playername", style: { display: "inline" }}, {type: "playername", style: { display: "inline" }},

View file

@ -178,6 +178,26 @@ svg.icon.timeout {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.mistvideo.casting .mistvideo-controls {
bottom: 0;
}
.mistvideo.casting .mistvideo-video {
filter: blur(1em);
}
.mistvideo.casting .mistvideo-maincontainer {
overflow: hidden;
}
.mistvideo.casting .mistvideo-casting {
position: absolute;
top: 50%;
transform: translateY(-50%);
text-align: center;
width: 100%;
font-size: 2em;
text-shadow: 1px 1px 5px #000a;
}
.mistvideo-error[data-passive] { .mistvideo-error[data-passive] {
bottom: auto; bottom: auto;
left: auto; left: auto;
@ -201,3 +221,20 @@ svg.icon.timeout {
transform-origin: 50% 50%; transform-origin: 50% 50%;
} }
.browser-ie .mist.icon.loading .spin { animation: none; } .browser-ie .mist.icon.loading .spin { animation: none; }
.mistvideo-chromecast {
display: flex;
}
google-cast-launcher {
width: 24px;
height: 24px;
--connected-color: $fill;
--disconnected-color: $semiFill;
}
google-cast-launcher.active {
--connected-color: $accent;
--disconnected-color: $fill;
}
.mistvideo.casting .mistvideo-slideshow_mode {
display: none;
}

View file

@ -106,3 +106,8 @@ input[type="checkbox"]:checked:after {
left: 0; left: 0;
font-size: 1.2em; font-size: 1.2em;
} }
.mistvideo.casting .mistvideo-forcePlayer,
.mistvideo.casting .mistvideo-forceType,
.mistvideo.casting .mistvideo-displayCombo {
display: none !important;
}

View file

@ -207,6 +207,7 @@ a {
animation-iteration-count: 1; animation-iteration-count: 1;
animation-timing-function: steps(1,end); animation-timing-function: steps(1,end);
} }
@keyframes mistvideo-appear { from { opacity: 0; } to { opacity: 1; } } @keyframes mistvideo-appear { from { opacity: 0; } to { opacity: 1; } }
svg.icon { svg.icon {