notifications to chromecast

Alexa, Google Home and Siri

Moderator: leecollings

jeroentje
Posts: 7
Joined: Thursday 01 June 2017 16:22
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: notifications to chromecast

Post by jeroentje »

hi, another question.

After sending a text or sound, the volume on the google mini stays the volume set on the stream2chromecast script.

Anyone an idea to ask on google home what the current volume is in the script, so the script can put the initial volume back after sending the text/sound?

thanks,
User avatar
thecosmicgate
Posts: 188
Joined: Monday 06 April 2015 14:37
Target OS: Linux
Domoticz version: newest
Location: The Netherlands / Hoeven
Contact:

Re: notifications to chromecast

Post by thecosmicgate »

jeroentje wrote:Hi,

same issue here. new google mini.
stream2chromecast stays "sending data" and takes 10 mins to stop.
The file is played well. I also use the french script with google TTS.

I've tried a lot (different versions of the .py files about messages and .py script and all the common solutions found in google for stream2chromecast), but still no solution.

Anyone ? " Sending data" hangs, you have to CNTL-C to stop:

Notification : testje
Volume : 0.2
Chromecast online
ip_addr: 192.168.0.200 device name:

-----------------------------------------

Stream2Chromecast version:0.6.3

Copyright (C) 2014-2016 Pat Carter
GNU General Public License v3.0
https://www.gnu.org/licenses/gpl-3.0.html

-----------------------------------------

ip_addr: 192.168.0.200 device name:
source is file: /tmp/message.mp3
local ip address: 192.168.0.202
OS identifies the mimetype as : audio/mpeg
URL & content-type: http://192.168.0.202:41791?/tmp/message.mp3 audio/mpeg
loading media...
192.168.0.200 - - [09/Sep/2018 16:18:03] "GET /?/tmp/message.mp3 HTTP/1.1" 200 -
sending data

and then it hangs :-(

Any help is appriciated !
So what's the working script you're using ?

Sent from my ONEPLUS A6003 using Tapatalk

It's nice to be important, but it's more important to be nice
Scooter ;)
glsf91
Posts: 58
Joined: Tuesday 14 November 2017 21:56
Target OS: Linux
Domoticz version:
Contact:

Re: notifications to chromecast

Post by glsf91 »

jeroentje wrote: Sunday 09 September 2018 16:21 Hi,

same issue here. new google mini.
stream2chromecast stays "sending data" and takes 10 mins to stop.
The file is played well. I also use the french script with google TTS.

I've tried a lot (different versions of the .py files about messages and .py script and all the common solutions found in google for stream2chromecast), but still no solution.

Anyone ? " Sending data" hangs, you have to CNTL-C to stop:

I think this is related to a firmware upgrade in june on the chromecast. I have the same problem I see.
But after changing the code a little bit it is working again. I'am not sure it is the right solution but it is working again for me.
I did not tested a lot.

Change this in cc_media_controller.py:

Code: Select all

def is_idle(self):
        """ return the IDLE state of the player """
        
        status = self.get_status()
        
        if status['media_status']  is None:
            if status['receiver_status'] is None:
                return True
            else:    
                return status['receiver_status'].get("statusText", "") == u"Ready To Cast"

        else:    
            return status['media_status'].get("playerState", "") == u"IDLE"
to this:

Code: Select all

def is_idle(self):
        """ return the IDLE state of the player """
        
        status = self.get_status()
        
        if status['media_status']  is None:
                return True
        else:    
            return status['media_status'].get("playerState", "") == u"IDLE"
Also working on my google mini.
User avatar
sincze
Posts: 1300
Joined: Monday 02 June 2014 22:46
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.4
Location: Netherlands / Breda Area
Contact:

Re: notifications to chromecast

Post by sincze »

glsf91 wrote: Sunday 16 September 2018 17:43
Spoiler: show

Code: Select all

def is_idle(self):
        """ return the IDLE state of the player """
        
        status = self.get_status()
        
        if status['media_status']  is None:
                return True
        else:    
            return status['media_status'].get("playerState", "") == u"IDLE"
Thanks for the update modified my code and indeed running smoothly without errors.
Pass2php
LAN: RFLink, P1, OTGW, MySensors
USB: RFXCom, ZWave, Sonoff 3
MQTT: ZIgbee2MQTT,
ZWAVE: Zwave-JS-UI
WIFI: Mi-light, Tasmota, Xiaomi Shelly
Solar: Omnik, PVOutput
Video: Kodi, Harmony HUB, Chromecast
Sensors: You name it I got 1.
mark.sellwood
Posts: 101
Joined: Tuesday 04 March 2014 10:33
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Surrey, UK
Contact:

Re: notifications to chromecast

Post by mark.sellwood »

Hi have made the change to cc_media_controller.py but still it hangs after the message sending data. I do hear the message.

Any suggestions?
3 x Pi, 1 Master, 2 Slaves, 1x Aeotec Z-Stick S2, 4xSP103 PIR, 5xPowerNode 1, 1xSmart Energy Switch Gen5, 4xFGSS101 Smoke Sensor, 2xFGD212, 9xFGS212 , 7xFGS221/2, 1xAD142 , 1xTKB TZ68E , 2xAeotec Multi Sensor, 3 x NodOn CRC-3-1-00.
glsf91
Posts: 58
Joined: Tuesday 14 November 2017 21:56
Target OS: Linux
Domoticz version:
Contact:

Re: notifications to chromecast

Post by glsf91 »

mark.sellwood wrote: Wednesday 19 September 2018 22:13 Hi have made the change to cc_media_controller.py but still it hangs after the message sending data. I do hear the message.

Any suggestions?
I also have sometime a hanging script. So I switched to pychromecast.
Warning: the programming below is maybe awfull but it is working (I'am not a programmer).

I use this with https://www.domoticz.com/forum/viewtopi ... 10#p175077.

Added a script play_chromecast.py:

Code: Select all

from __future__ import print_function
import time
import pychromecast
import threading
from time import sleep
import sys


args = sys.argv[1:]

if len(args) < 2:
    sys.exit("Usage: notification_google_home <text> <volume 0..1>")

messageUrl=args[0]
volume=float(args[1])

print("MessageUrl: ", messageUrl)
print("Volume: ", volume)


# Your Chromecast device Friendly Name
device_friendly_name = "Family room speaker"

chromecasts = pychromecast.get_chromecasts()

# select Chromecast device
cast = next(cc for cc in chromecasts if cc.device.friendly_name == device_friendly_name)

# wait for the device
cast.wait()
#print(cast.device)
#print(cast.status)

cast.set_volume(volume)
sleep(0.3)
#print(cast.status)

# get media controller
mc = cast.media_controller

start_playing = False

completion = threading.Event()

class StatusMediaListener:
    def __init__(self, name, cast):
        self.name = name
        self.cast= cast

    def new_media_status(self, status):
#        print('[',time.ctime(),' - ', self.name,'] status media change:')
#        print(status)
        if (status.player_is_idle and start_playing):
             completion.set()

listenerMedia = StatusMediaListener(cast.name, cast)
cast.media_controller.register_status_listener(listenerMedia)


# set online video url
mc.play_media(messageUrl, 'audio/mp3')

# blocks device
mc.block_until_active()
#print(mc.status)

# plays the video
mc.play()

start_playing = True

# poll so signal handlers still work
while not completion.wait(0.5):
        pass

Change device_friendly_name to yours.

and changed notification_chromecast.sh to:

Code: Select all

#!/bin/bash
# notification_google_home.sh Script de notification de message vocal sur la Google Home
# by JS Martin - 11/02/2018 - version 0.1

message=$1 # text message
volume=$2  # 0=auto 0.1=10% 1=100%
jingle=$3  # jingle track number (0=no track 1=default track)

echo "Started with: " $1 $2 $3

# ------ parameters ---------

# Autoset volume if  volume=0
Start_day="0700"
Start_night="2200"
Night_vol="0.4"
Day_vol="0.7"

# number of arg correction
case "$#" in
"1")
    volume="0"
    jingle="1"
    ;;
"2")
    jingle="1"
    ;;
*)
    echo "Usage: notification_google_home <text> [volume 0..1]"
    exit 1
    ;;
esac


# IP Google Home
IPGH="192.168.1.173"


echo "Notification : "$message

if [ $volume != "0" ]; then
   echo "Volume : "$volume
else
   CUR_TIME=`date +%H%M`
   if [ $CUR_TIME -ge $Start_day -a $CUR_TIME -le $Start_night ]; then
      echo "Day volume"
      volume=$Day_vol
   else
      echo "Night volume"
      volume=$Night_vol
   fi
   echo "Volume = automatique - set to "$volume
fi

# Exit if chromecast not online
ping -c1 -W 2 $IPGH >/dev/null
if [ $? -eq 0 ]
then echo "Chromecast online"
else echo "Chromecast offline"
exit 0
fi

if [ -f /tmp/message.mp3 ]
then
  rm /tmp/message.mp3
fi

#Text to MP3; afgekeken uit izSynth
#curl -s -G "http://api.naturalreaders.com/v4/tts/macspeak" --data "apikey=<apikey>&src=pw&r=22&s=1" --data-urlencode "t=$message" -o /tmp/message.mp3

# google gebruiken
#if [ ${#message} -lt 150 ]
#then
#  curl -s -G "http://translate.google.com/translate_tts" --data "ie=UTF-8&total=1&idx=0&client=tw-ob&&tl=nl-NL" --data-urlencode "q=$message" -A "Mozilla" --compressed -o /tmp/message.mp3
#else
#  /home/john/.local/bin/gtts-cli "$message"  -l 'nl' -o /tmp/message.mp3
#fi

#Microsoft Bing Speech API.  free 5000 per maand max 5 per sec.
python /home/john/bingSpeech/BingTTSGen.py --cache=/home/john/bingSpeech/cache --dest=/tmp/message.mp3 --lang=nl-NL  --voice="HannaRUS"  --fileformat=audio-16khz-32kbitrate-mono-mp3  --apikey=<apikey>  --text=''"$message"''


if [ -f /tmp/message.mp3 ]
then

# Problemen met stream2chromecast vanwege update chromecast juli 2018
  #Set Google Home volume
#  sudo python /home/john/stream2chromecast/stream2chromecast/stream2chromecast.py -devicename $IPGH -setvol $volume

  #MP3 to Google Home
#  sudo python /home/john/stream2chromecast/stream2chromecast/stream2chromecast.py -devicename $IPGH /tmp/message.mp3

   cp /tmp/message.mp3 /home/john/domoticz/www/message.mp3
   python3 /home/john/pychromecast/play_chromecast.py http://192.168.1.159:8080/message.mp3 $volume

fi

if [ -f /tmp/message.mp3 ]
then
  rm /tmp/message.mp3
fi
Of course adjust the path of the scripts and domoticz url in the last script to yours.
I think this works better but I did not test for a long time.

You need python 3.4+ (check with command: python3)
Install pychromecast with:
apt-get install python3-pip (if you don't have python3 pip)
python3 -m pip install pychromecast --user

Adjust <apikey>
Last edited by glsf91 on Sunday 13 January 2019 14:10, edited 3 times in total.
User avatar
sincze
Posts: 1300
Joined: Monday 02 June 2014 22:46
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.4
Location: Netherlands / Breda Area
Contact:

Re: notifications to chromecast

Post by sincze »

I thought... I had a good idea to move all my IOT stuff to a seperate VLAN.
My Google Chromecasts, Google Home , LG Smart TV's you name it.

After some modifications it all works.
Domoticz can reach the devices and control them, read the status.
... However... what is not working.. Stream2chromecast.

Code: Select all

-----------------------------------------

Stream2Chromecast version:0.6.3

Copyright (C) 2014-2016 Pat Carter
GNU General Public License v3.0
https://www.gnu.org/licenses/gpl-3.0.html

-----------------------------------------

ip_addr: 192.168.yy.yy device name:
source is file: /tmp/google/2f55628619d7d0366ece864b2cdf67d8.mp3
local ip address: 192.168.xx.xx
OS identifies the mimetype as : audio/mpeg
URL & content-type:  http://192.168.xx.xx:39121?/tmp/google/2f55628619d7d0366ece864b2cdf67d8.mp3 audio/mpeg
loading media...
I opened op the router VLAN Firewall (TCP & UDP) so the google home can reach the Domoticz machine where stream2chromecast is running from. However it seems it is unable to collect the file. It does adjust the playback volume to the correct level.
Where 192.168.yy.yy is the Google Home and 192.168.xx.xx is the Domoticz machine.

Code: Select all

user@raspberry-user:~ $ sudo iptables --list
iptables v1.6.0: can't initialize iptables table `filter': Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.
What did I miss :lol:
Pass2php
LAN: RFLink, P1, OTGW, MySensors
USB: RFXCom, ZWave, Sonoff 3
MQTT: ZIgbee2MQTT,
ZWAVE: Zwave-JS-UI
WIFI: Mi-light, Tasmota, Xiaomi Shelly
Solar: Omnik, PVOutput
Video: Kodi, Harmony HUB, Chromecast
Sensors: You name it I got 1.
User avatar
thecosmicgate
Posts: 188
Joined: Monday 06 April 2015 14:37
Target OS: Linux
Domoticz version: newest
Location: The Netherlands / Hoeven
Contact:

Re: notifications to chromecast

Post by thecosmicgate »

So what's the working script for now ?
Can't get this working . Still hangs .
Could anybody share his script or way how it's working

Verstuurd vanaf mijn ONEPLUS A6003 met Tapatalk

It's nice to be important, but it's more important to be nice
Scooter ;)
User avatar
sincze
Posts: 1300
Joined: Monday 02 June 2014 22:46
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.4
Location: Netherlands / Breda Area
Contact:

Re: notifications to chromecast

Post by sincze »

thecosmicgate wrote: Friday 19 October 2018 21:29 So what's the working script for now ?
Can't get this working . Still hangs .
Could anybody share his script or way how it's working

Verstuurd vanaf mijn ONEPLUS A6003 met Tapatalk
This stream2chromecast is the script I am using without any problems.(after modifying the cc_media_controller.py as well.
Spoiler: show

Code: Select all

#!/usr/bin/env python
"""
stream2chromecast.py: Chromecast media streamer for Linux

author: Pat Carter - https://github.com/Pat-Carter/stream2chromecast

version: 0.6.3

"""


# Copyright (C) 2014-2016 Pat Carter
#
# This file is part of Stream2chromecast.
#
# Stream2chromecast is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Stream2chromecast is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Stream2chromecast.  If not, see <http://www.gnu.org/licenses/>.


VERSION = "0.6.3"


import sys, os, errno
import signal

from cc_media_controller import CCMediaController
import cc_device_finder
import time

import BaseHTTPServer
import urllib
import mimetypes
from threading import Thread

import subprocess

import httplib
import urlparse

import socket

import tempfile



script_name = (sys.argv[0].split(os.sep))[-1]

USAGETEXT = """
Usage

Play a file:-
    %s <file>
    

Pause the current file:-
    %s -pause


Continue (un-pause) the current file:-
    %s -continue

        
Stop the current file playing:-
    %s -stop


Set the volume to a value between 0 & 1.0  (e.g. 0.5 = half volume):-
    %s -setvol <volume>


Adjust the volume up or down by 0.1:-
    %s -volup
    %s -voldown
    

Mute the volume:-
    %s -mute
    
           
Play an unsupported media type (e.g. an mpg file) using ffmpeg or avconv as a realtime transcoder (requires ffmpeg or avconv to be installed):-
    %s -transcode <file> 


Play remote file using a URL (e.g. a web video):
    %s -playurl <URL>

    
Display Chromecast status:-
    %s -status    
    
    
Search for all Chromecast devices on the network:-
    %s -devicelist
    
    
Additional option to specify an Chromecast device by name (or ip address) explicitly:
    e.g. to play a file on a specific device
    %s -devicename <chromecast device name> <file>
    
    
Additional option to specify the preferred transcoder tool when both ffmpeg & avconv are available
    e.g. to play and transcode a file using avconv
    %s -transcoder avconv -transcode <file>
    
    
Additional option to specify the port from which the media is streamed. This can be useful in a firewalled environment.
    e.g. to serve the media on port 8765
    %s -port 8765 <file>


Additional option to specify subtitles. Only WebVTT format is supported.
    e.g. to cast the subtitles on /path/to/subtitles.vtt
    %s -subtitles /path/to/subtitles.vtt <file>


Additional option to specify the port from which the subtitles is streamed. This can be useful in a firewalled environment.
    e.g. to serve the subtitles on port 8765
    %s -subtitles_port 8765 <file>


Additional option to specify the subtitles language. The language format is defined by RFC 5646.
    e.g. to serve the subtitles french subtitles
    %s -subtitles_language fr <file>

    
Additional option to supply custom parameters to the transcoder (ffmpeg or avconv) output
    e.g. to transcode the media with an output video bitrate of 1000k
    %s -transcode -transcodeopts '-b:v 1000k' <file>

    
Additional option to supply custom parameters to the transcoder input
    e.g. to transcode the media and seek to a position 15 minutes from the start of playback
    %s -transcode -transcodeinputopts '-ss 00:15:00' <file>
    
    
Additional option to specify the buffer size of the data returned from the transcoder. Increasing this can help when on a slow network.
    e.g. to specify a buffer size of 5 megabytes
    %s -transcode -transcodebufsize 5242880 <file>
    
""" % ((script_name,) * 21)


"""" 29-04-2018 Modified PID file location so www-data is able to write to the directory """
GOOGLE = os.path.join(tempfile.gettempdir(),"google/")
"""PIDFILE = os.path.join(tempfile.gettempdir(),"stream2chromecast_%s.pid")  """
PIDFILE = os.path.join(GOOGLE,"stream2chromecast_%s.pid") 

FFMPEG = 'ffmpeg %s -i "%s" -preset ultrafast -f mp4 -frag_duration 3000 -b:v 2000k -loglevel error %s -'
AVCONV = 'avconv %s -i "%s" -preset ultrafast -f mp4 -frag_duration 3000 -b:v 2000k -loglevel error %s -'



class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    content_type = "video/mp4"
    
    """ Handle HTTP requests for files which do not need transcoding """
    
    def do_GET(self):
        
        query = self.path.split("?",1)[-1]
        filepath = urllib.unquote_plus(query)
        
        self.suppress_socket_error_report = None
        
        self.send_headers(filepath)       
        
        print "sending data"      
        try: 
            self.write_response(filepath)
        except socket.error, e:     
            if isinstance(e.args, tuple):
                if e[0] in (errno.EPIPE, errno.ECONNRESET):
                   print "disconnected"
                   self.suppress_socket_error_report = True
                   return
            
            raise


    def handle_one_request(self):
        try:
            return BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self)
        except socket.error:
            if not self.suppress_socket_error_report:
                raise


    def finish(self):
        try:
            return BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
        except socket.error:
            if not self.suppress_socket_error_report:
                raise


    def send_headers(self, filepath):
        self.protocol_version = "HTTP/1.1"
        self.send_response(200)
        self.send_header("Content-type", self.content_type)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header("Transfer-Encoding", "chunked")
        self.end_headers()    


    def write_response(self, filepath):
        with open(filepath, "rb") as f:           
            while True:
                line = f.read(1024)
                if len(line) == 0:
                    break
            
                chunk_size = "%0.2X" % len(line)
                self.wfile.write(chunk_size)
                self.wfile.write("\r\n")
                self.wfile.write(line) 
                self.wfile.write("\r\n")  
                
        self.wfile.write("0")
        self.wfile.write("\r\n\r\n")                             



class TranscodingRequestHandler(RequestHandler):
    """ Handle HTTP requests for files which require realtime transcoding with ffmpeg """
    transcoder_command = FFMPEG
    transcode_options = ""
    transcode_input_options = ""    
    bufsize = 0
                    
    def write_response(self, filepath):
        if self.bufsize != 0:
            print "transcode buffer size:", self.bufsize
        
        ffmpeg_command = self.transcoder_command % (self.transcode_input_options, filepath, self.transcode_options) 
        
        ffmpeg_process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, shell=True, bufsize=self.bufsize)       

        for line in ffmpeg_process.stdout:
            chunk_size = "%0.2X" % len(line)
            self.wfile.write(chunk_size)
            self.wfile.write("\r\n")
            self.wfile.write(line) 
            self.wfile.write("\r\n")            
            
        self.wfile.write("0")
        self.wfile.write("\r\n\r\n")



class SubRequestHandler(RequestHandler):
    """ Handle HTTP requests for subtitles files """
    content_type = "text/vtt;charset=utf-8"



            
def get_transcoder_cmds(preferred_transcoder=None):
    """ establish which transcoder utility to use depending on what is installed """
    probe_cmd = None
    transcoder_cmd = None
    
    ffmpeg_installed = is_transcoder_installed("ffmpeg")
    avconv_installed = is_transcoder_installed("avconv")  
    
    # if anything other than avconv is preferred, try to use ffmpeg otherwise use avconv    
    if preferred_transcoder != "avconv":
        if ffmpeg_installed:
            transcoder_cmd = "ffmpeg"
            probe_cmd = "ffprobe"
        elif avconv_installed:
            print "unable to find ffmpeg - using avconv"
            transcoder_cmd = "avconv"
            probe_cmd = "avprobe"
    
    # otherwise, avconv is preferred, so try to use avconv, followed by ffmpeg  
    else:
        if avconv_installed:
            transcoder_cmd = "avconv"
            probe_cmd = "avprobe"
        elif ffmpeg_installed:
            print "unable to find avconv - using ffmpeg"
            transcoder_cmd = "ffmpeg"
            probe_cmd = "ffprobe"
            
    return transcoder_cmd, probe_cmd
    
    
                

def is_transcoder_installed(transcoder_application):
    """ check for an installation of either ffmpeg or avconv """
    try:
        subprocess.check_output([transcoder_application, "-version"])
        return True
    except OSError:
        return False
       



def kill_old_pid(device_ip):
    """ attempts to kill a previously running instance of this application casting to the specified device. """
    pid_file = PIDFILE % device_ip
    try:
        with open(pid_file, "r") as pidfile:
            pid = int(pidfile.read())
            os.killpg(pid, signal.SIGTERM)    
    except:
        pass
               


def save_pid(device_ip):
    """ saves the process id of this application casting to the specified device in a pid file. """
    pid_file = PIDFILE % device_ip
    with open(pid_file, "w") as pidfile:
        pidfile.write("%d" %  os.getpid())




def get_mimetype(filename, ffprobe_cmd=None):
    """ find the container format of the file """
    # default value
    mimetype = "video/mp4"
    
    
    # guess based on filename extension
    guess = mimetypes.guess_type(filename)[0]
    if guess is not None:
        if guess.lower().startswith("video/") or guess.lower().startswith("audio/"):
            mimetype = guess
      
        
    # use the OS file command...
    try:
        file_cmd = 'file --mime-type -b "%s"' % filename
        file_mimetype = subprocess.check_output(file_cmd, shell=True).strip().lower()
        
        if file_mimetype.startswith("video/") or file_mimetype.startswith("audio/"):
            mimetype = file_mimetype
            
            print "OS identifies the mimetype as :", mimetype
            return mimetype
    except:
        pass
    
    
    # use ffmpeg/avconv if installed
    if ffprobe_cmd is None:
        return mimetype
    
    # ffmpeg/avconv is installed
    has_video = False
    has_audio = False
    format_name = None
    
    ffprobe_cmd = '%s -show_streams -show_format "%s"' % (ffprobe_cmd, filename)
    ffmpeg_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, shell=True)

    for line in ffmpeg_process.stdout:
        if line.startswith("codec_type=audio"):
            has_audio = True
        elif line.startswith("codec_type=video"):
            has_video = True    
        elif line.startswith("format_name="):
            name, value = line.split("=")
            format_name = value.strip().lower().split(",")


    # use the default if it isn't possible to identify the format type
    if format_name is None:
        return mimetype
    
    
    if has_video:
        mimetype = "video/"
    else:
        mimetype = "audio/"
        
    if "mp4" in format_name:
        mimetype += "mp4"            
    elif "webm" in format_name:
        mimetype += "webm"
    elif "ogg" in format_name:
        mimetype += "ogg"        
    elif "mp3" in format_name:
        mimetype = "audio/mpeg"
    elif "wav" in format_name:
        mimetype = "audio/wav" 
    else:   
        mimetype += "mp4"     
        
    return mimetype
    
            
            
def play(filename, transcode=False, transcoder=None, transcode_options=None, transcode_input_options=None,
         transcode_bufsize=0, device_name=None, server_ip=None, server_port=None, server_external_port=None,
         subtitles=None, subtitles_port=None, subtitles_language=None):
    """ play a local file or transcode from a file or URL and stream to the chromecast """
    
    print_ident()
    
    
    cast = CCMediaController(device_name=device_name)
    
    kill_old_pid(cast.host)
    save_pid(cast.host)    


    if os.path.isfile(filename):
        filename = os.path.abspath(filename)
        print "source is file: %s" % filename
    else:
        if transcode and (filename.lower().startswith("http://") or filename.lower().startswith("https://") or filename.lower().startswith("rtsp://")):
            print "source is URL: %s" % filename
        else: 
            sys.exit("media file %s not found" % filename)
        

    
    transcoder_cmd, probe_cmd = get_transcoder_cmds(preferred_transcoder=transcoder)
    

    status = cast.get_status()
    webserver_ip = status['client'][0]
    
    if server_ip is None:
	server_ip = webserver_ip
    
    print "local ip address:", webserver_ip
        
    
    req_handler = RequestHandler
    
    if transcode:
        if transcoder_cmd in ("ffmpeg", "avconv"):
            req_handler = TranscodingRequestHandler
            
            if transcoder_cmd == "ffmpeg":  
                req_handler.transcoder_command = FFMPEG
            else:
                req_handler.transcoder_command = AVCONV
                
            if transcode_options is not None:    
                req_handler.transcode_options = transcode_options
                
            if transcode_input_options is not None:    
                req_handler.transcode_input_options = transcode_input_options                
                
            req_handler.bufsize = transcode_bufsize
        else:
            print "No transcoder is installed. Attempting standard playback"
   
    
    
    if req_handler == RequestHandler:
        req_handler.content_type = get_mimetype(filename, probe_cmd)
        
    
    # create a webserver to handle a single request for the media file on either a free port or on a specific port if passed in the port parameter   
    port = 0    
    
    if server_port is not None:
        port = int(server_port)
        
    server = BaseHTTPServer.HTTPServer((webserver_ip, port), req_handler)
    
    if server_external_port is None:
    	server_external_port = server.server_port

    thread = Thread(target=server.handle_request)
    thread.start()


    url = "http://%s:%s?%s" % (server_ip, str(server_external_port), urllib.quote_plus(filename, "/"))

    print "URL & content-type: ", url, req_handler.content_type


    # create another webserver to handle a request for the subtitles file, if specified in the subtitles parameter
    sub = None

    if subtitles:
        if os.path.isfile(subtitles):
            sub_port = 0

            if subtitles_port is not None:
                sub_port = int(subtitles_port)

            sub_server = BaseHTTPServer.HTTPServer((webserver_ip, sub_port), SubRequestHandler)
            thread2 = Thread(target=sub_server.handle_request)
            thread2.start()

            sub = "http://%s:%s?%s" % (webserver_ip, str(sub_server.server_port), urllib.quote_plus(subtitles, "/"))
            print "sub URL: ", sub
        else:
            print "Subtitles file %s not found" % subtitles


    load(cast, url, req_handler.content_type, sub, subtitles_language)

    
    

def load(cast, url, mimetype, sub=None, sub_language=None):
    """ load a chromecast instance with a url and wait for idle state """
    try:
        print "loading media..."
        
        cast.load(url, mimetype, sub, sub_language)
        
        # wait for playback to complete before exiting
        print "waiting for player to finish - press ctrl-c to stop..."    
        
        idle = False
        while not idle:
            time.sleep(1)
            idle = cast.is_idle()
   
    except KeyboardInterrupt:
        print
        print "stopping..."
        cast.stop()
        
    finally:
        print "done"
    
    
    
def playurl(url, device_name=None):
    """ play a remote HTTP resource on the chromecast """
    
    print_ident()

    def get_resp(url):
        url_parsed = urlparse.urlparse(url)
    
        scheme = url_parsed.scheme
        host = url_parsed.netloc
        path = url.split(host, 1)[-1]
        
        conn = None
        if scheme == "https":
            conn = httplib.HTTPSConnection(host)
        else:
            conn = httplib.HTTPConnection(host)
        
        conn.request("HEAD", path)
    
        resp = conn.getresponse()
        return resp


    def get_full_url(url, location):
        url_parsed = urlparse.urlparse(url)

        scheme = url_parsed.scheme
        host = url_parsed.netloc

        if location.startswith("/") is False:
            path = url.split(host, 1)[-1] 
            if path.endswith("/"):
                path = path.rsplit("/", 2)[0]
            else:
                path = path.rsplit("/", 1)[0] + "/"
            location = path + location

        full_url = scheme + "://" + host + location

        return full_url


    resp = get_resp(url)

    if resp.status != 200:
        redirect_codes = [ 301, 302, 303, 307, 308 ]
        if resp.status in redirect_codes:
            redirects = 0
            while resp.status in redirect_codes:
                redirects += 1
                if redirects > 9:
                    sys.exit("HTTP Error: Too many redirects")
                headers = resp.getheaders()
                for header in headers:
                    if len(header) > 1:
                        if header[0].lower() == "location":
                            redirect_location = header[1]
                if redirect_location.startswith("http") is False:
                    redirect_location = get_full_url(url, redirect_location)
                print "Redirecting to " + redirect_location
                resp = get_resp(redirect_location)
            if resp.status != 200:
                sys.exit("HTTP error:" + str(resp.status) + " - " + resp.reason)
        else:
            sys.exit("HTTP error:" + str(resp.status) + " - " + resp.reason)
        
    print "Found HTTP resource"
    
    headers = resp.getheaders()
    
    mimetype = None
    
    for header in headers:
        if len(header) > 1:
            if header[0].lower() == "content-type":
                mimetype = header[1]
    
    if mimetype != None:            
        print "content-type:", mimetype
    else:
        mimetype = "video/mp4"
        print "resource does not specify mimetype - using default:", mimetype
    
    cast = CCMediaController(device_name=device_name)
    load(cast, url, mimetype)    
    

            
    
def pause(device_name=None):
    """ pause playback """
    CCMediaController(device_name=device_name).pause()


def unpause(device_name=None):
    """ continue playback """
    CCMediaController(device_name=device_name).play()    

        
def stop(device_name=None):
    """ stop playback and quit the media player app on the chromecast """
    CCMediaController(device_name=device_name).stop()

def get_volume(device_name=None):
    """ print the status of the chromecast device """
    print CCMediaController(device_name=device_name).get_volume()

def get_status(device_name=None):
    """ print the status of the chromecast device """
    print CCMediaController(device_name=device_name).get_status()

def volume_up(device_name=None):
    """ raise the volume by 0.1 """
    CCMediaController(device_name=device_name).set_volume_up()


def volume_down(device_name=None):
    """ lower the volume by 0.1 """
    CCMediaController(device_name=device_name).set_volume_down()


def set_volume(v, device_name=None):
    """ set the volume to level between 0 and 1 """
    CCMediaController(device_name=device_name).set_volume(v)
    
    
def list_devices():
    print "Searching for devices, please wait..."
    device_ips = cc_device_finder.search_network(device_limit=None, time_limit=10)
    
    print "%d devices found" % len(device_ips)
    
    for device_ip in device_ips:
        print device_ip, ":", cc_device_finder.get_device_name(device_ip)
        

def print_ident():
    """ display initial messages """
    print
    print "-----------------------------------------"   
    print     
    print "Stream2Chromecast version:%s" % VERSION        
    print 
    print "Copyright (C) 2014-2016 Pat Carter"
    print "GNU General Public License v3.0" 
    print "https://www.gnu.org/licenses/gpl-3.0.html"
    print    
    print "-----------------------------------------"
    print 
    

def validate_args(args):
    """ validate that there are the correct number of arguments """
    if len(args) < 1:
        sys.exit(USAGETEXT)
        
    if args[0] == "-setvol" and len(args) < 2:
        sys.exit(USAGETEXT) 
    


def get_named_arg_value(arg_name, args, integer=False):
    """ get a argument value by name """
    arg_val = None
    if arg_name in args:

        arg_pos = args.index(arg_name)
        arg_name = args.pop(arg_pos)
        
        if len(args) > (arg_pos + 1):
            arg_val = args.pop(arg_pos)
    
    if integer:
        int_arg_val = 0
        if arg_val is not None:
            try:
                int_arg_val = int(arg_val)
            except ValueError:
                print "Invalid integer parameter, defaulting to zero. Parameter name:", arg_name
                
        arg_val = int_arg_val
                
    return arg_val
    
        

def run():
    """ main execution """
    args = sys.argv[1:]
    
    
    # optional device name parm. if not specified, device_name = None (the first device found will be used).
    device_name = get_named_arg_value("-devicename", args)
    
    # optional transcoder parm. if not specified, ffmpeg will be used, if installed, otherwise avconv.
    transcoder = get_named_arg_value("-transcoder", args)

    server_ip = get_named_arg_value("-server_ip", args) 
    
    # optional server port parm. if not specified, a random available port will be used
    server_port = get_named_arg_value("-port", args)

    server_external_port = get_named_arg_value("-external_port", args)     
    
    # optional transcode options parm. if specified, these options will be passed to the transcoder to be applied to the output
    transcode_options = get_named_arg_value("-transcodeopts", args)     
    
    # optional transcode options parm. if specified, these options will be passed to the transcoder to be applied to the input data
    transcode_input_options = get_named_arg_value("-transcodeinputopts", args)      
    
    # optional transcode bufsize parm. if specified, the transcoder will buffer approximately this many bytes of output
    transcode_bufsize = get_named_arg_value("-transcodebufsize", args, integer=True)

    # optional subtitle parm. if specified, the specified subtitles will be played.
    subtitles = get_named_arg_value("-subtitles", args)

    # optional subtitle_port parm. if not specified, a random available port will be used.
    subtitles_port = get_named_arg_value("-subtitles_port", args)

    # optional subtitle_language parm. if not specified en-US will be used.
    subtitles_language = get_named_arg_value("-subtitles_language", args)


        
    validate_args(args)
    
    if args[0] == "-stop":
        stop(device_name=device_name)
        
    elif args[0] == "-pause":
        pause(device_name=device_name)        
    
    elif args[0] == "-continue":
        unpause(device_name=device_name)           
    
    elif args[0] == "-status":
        get_status(device_name=device_name)

    elif args[0] == "-getvol":
        get_volume(device_name=device_name)

    elif args[0] == "-setvol":
        set_volume(float(args[1]), device_name=device_name)

    elif args[0] == "-volup":
        volume_up(device_name=device_name)

    elif args[0] == "-voldown":
        volume_down(device_name=device_name)

    elif args[0] == "-mute":
        set_volume(0, device_name=device_name)

    elif args[0] == "-transcode":    
        arg2 = args[1]  
        play(arg2, transcode=True, transcoder=transcoder, transcode_options=transcode_options, transcode_input_options=transcode_input_options, transcode_bufsize=transcode_bufsize,
             device_name=device_name, server_ip=server_ip, server_port=server_port, server_external_port=server_external_port, subtitles=subtitles, subtitles_port=subtitles_port,
             subtitles_language=subtitles_language)
        
    elif args[0] == "-playurl":    
        arg2 = args[1]  
        playurl(arg2, device_name=device_name)                          
        
    elif args[0] == "-devicelist":
        list_devices()
            
    else:
        play(args[0], device_name=device_name, server_ip=server_ip, server_port=server_port, server_external_port=server_external_port, subtitles=subtitles,
             subtitles_port=subtitles_port, subtitles_language=subtitles_language)
        
            
if __name__ == "__main__":
    run()

cc_media_controller.py
Spoiler: show

Code: Select all

"""
Provides a control interface to the Chromecast Media Player app

version 0.2.1

"""


# Copyright (C) 2014-2016 Pat Carter
#
# This file is part of Stream2chromecast.
#
# Stream2chromecast is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Stream2chromecast is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Stream2chromecast.  If not, see <http://www.gnu.org/licenses/>.



import socket, ssl, select
import json
import sys
import time
import re

import cc_device_finder
import cc_message


MEDIAPLAYER_APPID = "CC1AD845"
 

class CCMediaController():
    def __init__(self, device_name=None):
        """ initialise """
        
        self.host = self.get_device(device_name)

        self.sock = None
        
        self.request_id = 1
        self.source_id = "sender-0"

        self.receiver_app_status = None
        self.media_status = None
        self.volume_status = None
        self.current_applications = None
        
    
    
    def get_device(self, device_name):
        """ get the device ip address """
        
        host = None
        dummy = None

        is_ip_addr = device_name is not None and re.match( "[0-9]+.[0-9]+.[0-9]+.[0-9]+$", device_name) is not None
        
        if is_ip_addr:
            host = device_name
            try:
                #print "ip_addr:", host.encode('utf-8'), "device name:", cc_device_finder.get_device_name(host).encode('utf-8')
                print >> sys.stderr, "ip_addr:", host.encode('utf-8'), "device name:", cc_device_finder.get_device_name(host).encode('utf-8')

            except socket.error:
                sys.exit("No Chromecast found on ip:" + host)
        else:
            host, name = cc_device_finder.find_device(name=device_name)
            if host is None:
                sys.exit("No Chromecast found on the network")
                
            #print "device name:", name    
            print >> sys.stderr, "device name:", name 
        return host
        
        
        
    def open_socket(self):
        """ open a socket if there is not currently one open """
        
        if self.sock is None:
            self.sock = socket.socket()
            self.sock = ssl.wrap_socket(self.sock)

            self.sock.connect((self.host,8009))

                
    def close_socket(self):
        """ close the socket if there is one open """
        
        if self.sock is not None:
            self.sock.close()
            
        self.sock = None



    def send_data(self, namespace, data_dict):
        """ send data to the device in binary format"""
        
        data = json.dumps(data_dict)
        
        #print "Sending: ", namespace, data
        
        msg = cc_message.format_message(self.source_id, self.destination_id, namespace, data)
        
        self.sock.write(msg)

        
        
    def read_message(self):
        """ read a complete message from the device """

        data = ""
        while len(data) < 4:
            data += self.sock.recv(4)
        
        msg_length, data = cc_message.extract_length_header(data) 
        while len(data) < msg_length:
            data += self.sock.recv(2048)
            
       
        message_dict = cc_message.extract_message(data)
        
        message = {}
        
        try:
            message = json.loads(message_dict['data'])
        except:
            pass
        
        #print message_dict['namespace']
        #print json.dumps(message, indent=4, separators=(',', ': '))
        
        return message   
        
         
    
    def get_response(self, request_id):
        """ get the response matching the original request id """
        
        resp = {}
        
        count = 0
        while len(resp) == 0:
            msg = self.read_message()
            
            msg_type = msg.get("type", msg.get("responseType", ""))
            
            if msg_type == "PING":
                data = {"type":"PONG"}
                namespace = "urn:x-cast:com.google.cast.tp.heartbeat"
                self.send_data(namespace, data) 
                
                # if 30 ping/pong messages are received without a response to the request_id, 
                # assume no response is coming
                count += 1
                if count == 30:
                    return resp
                
            elif msg_type == "RECEIVER_STATUS":
                self.update_receiver_status_data(msg)
                
            elif msg_type == "MEDIA_STATUS":
                self.update_media_status_data(msg)
            
            if "requestId" in msg.keys() and msg['requestId'] == request_id:
                resp = msg
                
        return resp



    def send_msg_with_response(self, namespace, data):
        """ send a request to the device and wait for a response matching the request id """
        
        self.request_id += 1
        data['requestId'] = self.request_id
        
        self.send_data(namespace, data)
        
        return self.get_response(self.request_id)

            
        
    def update_receiver_status_data(self, msg):
        """ update the status for the Media Player app if it is running """
        
        self.receiver_app_status = None
        
        if msg.has_key('status'):
            status = msg['status']
            if status.has_key('applications'):
                self.current_applications = status['applications']
                for application in self.current_applications:
                    if application.get("appId") == MEDIAPLAYER_APPID:
                        self.receiver_app_status = application
                        
                        
            if status.has_key('volume'):
                self.volume_status = status['volume']
                        
                        
                        
    def update_media_status_data(self, msg): 
        """ update the media status if there is any media loaded """
        
        self.media_status = None
        
        status = msg.get("status", [])
        if len(status) > 0:  
            self.media_status = status[0] # status is an array - selecting the first result..?                 


         
        
    def connect(self, destination_id):  
        """ connect to to the receiver or the media transport """
        
        if self.sock is None:
            self.open_socket()
                     
        self.destination_id = destination_id
        
        data = {"type":"CONNECT","origin":{}}
        namespace = "urn:x-cast:com.google.cast.tp.connection"
        self.send_data(namespace, data)
        
        
    
    def get_receiver_status(self):
        """ send a status request to the receiver """
        
        data = {"type":"GET_STATUS"}
        namespace = "urn:x-cast:com.google.cast.receiver"
        self.send_msg_with_response(namespace, data)
                
    
    
    def get_media_status(self):
        """ send a status request to the media player """
        
        data = {"type":"GET_STATUS"}
        namespace = "urn:x-cast:com.google.cast.media"
        self.send_msg_with_response(namespace, data)   
            
            
                    
    def load(self, content_url, content_type, sub, sub_language):
        """ Launch the player app, load & play a URL """
        
        self.connect("receiver-0")

        self.get_receiver_status()
        
        # we only set the receiver status for MEDIAPLAYER - so if it is set, the app is currenty running
        if self.receiver_app_status is None:
            data = {"type":"LAUNCH","appId":MEDIAPLAYER_APPID}
            namespace = "urn:x-cast:com.google.cast.receiver"
            self.send_msg_with_response(namespace, data)
            
            # if there is still no receiver app status the launch failed.
            if self.receiver_app_status is None:
                self.close_socket()
                sys.exit("Cannot launch the Media Player app")
                
        
        session_id = str(self.receiver_app_status['sessionId'])
        transport_id = str(self.receiver_app_status['transportId'])

        self.connect(transport_id)

        data = {"type":"LOAD",
                "sessionId":session_id,
                "media":{
                    "contentId":content_url,
                    "streamType":"buffered",
                    "contentType":content_type,
                    },
                "autoplay":True,
                "currentTime":0,
                "customData":{
                    "payload":{
                        "title:":""
                        }
                    }
                }


        if sub:        
            if sub_language is None:
                sub_language = "en-US"
                
            data["media"].update({
                                "textTrackStyle":{
                                    'backgroundColor':'#FFFFFF00'
                                },
                                "tracks": [{"trackId": 1,
                                            "trackContentId": sub,
                                            "type": "TEXT",
                                            "language": sub_language,
                                            "subtype": "SUBTITLES",
                                            "name": "Englishx",
                                            "trackContentType": "text/vtt",
                                           }],
                                })
            data["activeTrackIds"] = [1]

        
        namespace = "urn:x-cast:com.google.cast.media"
        resp = self.send_msg_with_response(namespace, data)


        # wait for the player to return "BUFFERING", "PLAYING" or "IDLE"
        if resp.get("type", "") == "MEDIA_STATUS":            
            player_state = ""
            while player_state != "PLAYING" and player_state != "IDLE" and player_state != "BUFFERING":
                time.sleep(2)        
                
                self.get_media_status()
                
                if self.media_status != None:
                    player_state = self.media_status.get("playerState", "")

                
        self.close_socket()       


            
    def control(self, command, parameters={}):      
        """ send a control command to the player """
          
        self.connect("receiver-0")

        self.get_receiver_status()
        
        if self.receiver_app_status is None:
            print >> sys.stderr, "No media player app running"
            self.close_socket()
            return      
        
        transport_id = str(self.receiver_app_status['transportId'])       
        
        self.connect(transport_id)
        
        self.get_media_status()
        
        media_session_id = 1
        if self.media_status is not None:
            media_session_id = self.media_status['mediaSessionId']
                                                                     
        data = {"type":command, "mediaSessionId":media_session_id}
        data.update(parameters)  # for additional parameters
        
        namespace = "urn:x-cast:com.google.cast.media"
        self.send_msg_with_response(namespace, data)
        
        self.close_socket()
                       
    
    
    def get_status(self):
        """ get the receiver and media status """
        
        self.connect("receiver-0")

        self.get_receiver_status()
        
        if self.receiver_app_status is not None:   
        	transport_id = str(self.receiver_app_status['transportId']) 
        	self.connect(transport_id)
        	self.get_media_status()
        
        application_list = []
        if self.current_applications is not None:
            for application in self.current_applications:
                application_list.append({
                    'appId':application.get('appId', ""), 
                    'displayName':application.get('displayName', ""),  
                    'statusText':application.get('statusText', "")})
        
        status = {'receiver_status':self.receiver_app_status, 
                  'media_status':self.media_status, 
                  'host':self.host, 
                  'client':self.sock.getsockname(),
                  'applications':application_list}
                
        self.close_socket()
        
        return status
        
        
        
#    def is_idle(self):
#        """ return the IDLE state of the player """
        
#        status = self.get_status()
        
#        if status['media_status']  is None:
#            if status['receiver_status'] is None:
#                return True
#            else:    
#                return status['receiver_status'].get("statusText", "") == u"Ready To Cast"

#        else:    
#            return status['media_status'].get("playerState", "") == u"IDLE"

# https://www.domoticz.com/forum/viewtopic.php?f=69&t=22610&view=unread&sid=ad3e2da7f9d4eee231d26e7e63558869#unread
# Added 16-09-2918

    def is_idle(self):
        """ return the IDLE state of the player """
        
        status = self.get_status()
        
        if status['media_status']  is None:
                return True
        else:    
            return status['media_status'].get("playerState", "") == u"IDLE"

       
       

    def pause(self):
        """ pause """
        self.control("PAUSE") 
            
    def play(self):
        """ unpause """
        self.control("PLAY")   
        
    def stop(self):
        """ stop """
        self.control("STOP")
        self.connect("receiver-0")

        data = {"type":"STOP"}
        namespace = "urn:x-cast:com.google.cast.receiver"
        self.send_msg_with_response(namespace, data)  
        
        self.close_socket() 
         
        
        
        
    def set_volume(self, level):
        """ set the receiver volume - a float value in level for absolute level or "+" / "-" indicates up or down"""
        
        self.connect("receiver-0")

        if level in ("+", "-"):
            self.get_receiver_status()
        
            if self.volume_status is not None:
                curr_level = self.volume_status['level']
                if level == "+":
                    level = 0.1 + curr_level
                elif level == "-":
                    level = curr_level - 0.1
            
        
        data = {"type":"SET_VOLUME", "volume":{"muted":False, "level":level} }
        namespace = "urn:x-cast:com.google.cast.receiver"
        self.send_msg_with_response(namespace, data)  
        
        self.close_socket() 
        
        
    def get_volume_ext(self):
        """ get the current volume level """

        self.get_status()
        
        vol = None
        
        if self.volume_status is not None:
            vol = self.volume_status.get('level', None)
                
        return vol
    
    def get_volume(self):
        """ get the current volume level """
        self.get_status()
        
        vol = None
        
        if self.volume_status is not None:
            vol = self.volume_status.get('level', None)
                
        return vol
                         
                              
                                
    def set_volume_up(self):
        """ increase volume by one step """
        self.set_volume("+")
            

            
    def set_volume_down(self):
        """ decrease volume by one step """
        self.set_volume("-")    
            
Pass2php
LAN: RFLink, P1, OTGW, MySensors
USB: RFXCom, ZWave, Sonoff 3
MQTT: ZIgbee2MQTT,
ZWAVE: Zwave-JS-UI
WIFI: Mi-light, Tasmota, Xiaomi Shelly
Solar: Omnik, PVOutput
Video: Kodi, Harmony HUB, Chromecast
Sensors: You name it I got 1.
User avatar
thecosmicgate
Posts: 188
Joined: Monday 06 April 2015 14:37
Target OS: Linux
Domoticz version: newest
Location: The Netherlands / Hoeven
Contact:

Re: notifications to chromecast

Post by thecosmicgate »

sincze wrote: Saturday 20 October 2018 18:17
thecosmicgate wrote: Friday 19 October 2018 21:29 So what's the working script for now ?
Can't get this working . Still hangs .
Could anybody share his script or way how it's working

Verstuurd vanaf mijn ONEPLUS A6003 met Tapatalk
This stream2chromecast is the script I am using without any problems.(after modifying the cc_media_controller.py as well.
Spoiler: show

Code: Select all

#!/usr/bin/env python
"""
stream2chromecast.py: Chromecast media streamer for Linux

author: Pat Carter - https://github.com/Pat-Carter/stream2chromecast

version: 0.6.3

"""


# Copyright (C) 2014-2016 Pat Carter
#
# This file is part of Stream2chromecast.
#
# Stream2chromecast is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Stream2chromecast is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Stream2chromecast.  If not, see <http://www.gnu.org/licenses/>.


VERSION = "0.6.3"


import sys, os, errno
import signal

from cc_media_controller import CCMediaController
import cc_device_finder
import time

import BaseHTTPServer
import urllib
import mimetypes
from threading import Thread

import subprocess

import httplib
import urlparse

import socket

import tempfile



script_name = (sys.argv[0].split(os.sep))[-1]

USAGETEXT = """
Usage

Play a file:-
    %s <file>
    

Pause the current file:-
    %s -pause


Continue (un-pause) the current file:-
    %s -continue

        
Stop the current file playing:-
    %s -stop


Set the volume to a value between 0 & 1.0  (e.g. 0.5 = half volume):-
    %s -setvol <volume>


Adjust the volume up or down by 0.1:-
    %s -volup
    %s -voldown
    

Mute the volume:-
    %s -mute
    
           
Play an unsupported media type (e.g. an mpg file) using ffmpeg or avconv as a realtime transcoder (requires ffmpeg or avconv to be installed):-
    %s -transcode <file> 


Play remote file using a URL (e.g. a web video):
    %s -playurl <URL>

    
Display Chromecast status:-
    %s -status    
    
    
Search for all Chromecast devices on the network:-
    %s -devicelist
    
    
Additional option to specify an Chromecast device by name (or ip address) explicitly:
    e.g. to play a file on a specific device
    %s -devicename <chromecast device name> <file>
    
    
Additional option to specify the preferred transcoder tool when both ffmpeg & avconv are available
    e.g. to play and transcode a file using avconv
    %s -transcoder avconv -transcode <file>
    
    
Additional option to specify the port from which the media is streamed. This can be useful in a firewalled environment.
    e.g. to serve the media on port 8765
    %s -port 8765 <file>


Additional option to specify subtitles. Only WebVTT format is supported.
    e.g. to cast the subtitles on /path/to/subtitles.vtt
    %s -subtitles /path/to/subtitles.vtt <file>


Additional option to specify the port from which the subtitles is streamed. This can be useful in a firewalled environment.
    e.g. to serve the subtitles on port 8765
    %s -subtitles_port 8765 <file>


Additional option to specify the subtitles language. The language format is defined by RFC 5646.
    e.g. to serve the subtitles french subtitles
    %s -subtitles_language fr <file>

    
Additional option to supply custom parameters to the transcoder (ffmpeg or avconv) output
    e.g. to transcode the media with an output video bitrate of 1000k
    %s -transcode -transcodeopts '-b:v 1000k' <file>

    
Additional option to supply custom parameters to the transcoder input
    e.g. to transcode the media and seek to a position 15 minutes from the start of playback
    %s -transcode -transcodeinputopts '-ss 00:15:00' <file>
    
    
Additional option to specify the buffer size of the data returned from the transcoder. Increasing this can help when on a slow network.
    e.g. to specify a buffer size of 5 megabytes
    %s -transcode -transcodebufsize 5242880 <file>
    
""" % ((script_name,) * 21)


"""" 29-04-2018 Modified PID file location so www-data is able to write to the directory """
GOOGLE = os.path.join(tempfile.gettempdir(),"google/")
"""PIDFILE = os.path.join(tempfile.gettempdir(),"stream2chromecast_%s.pid")  """
PIDFILE = os.path.join(GOOGLE,"stream2chromecast_%s.pid") 

FFMPEG = 'ffmpeg %s -i "%s" -preset ultrafast -f mp4 -frag_duration 3000 -b:v 2000k -loglevel error %s -'
AVCONV = 'avconv %s -i "%s" -preset ultrafast -f mp4 -frag_duration 3000 -b:v 2000k -loglevel error %s -'



class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    content_type = "video/mp4"
    
    """ Handle HTTP requests for files which do not need transcoding """
    
    def do_GET(self):
        
        query = self.path.split("?",1)[-1]
        filepath = urllib.unquote_plus(query)
        
        self.suppress_socket_error_report = None
        
        self.send_headers(filepath)       
        
        print "sending data"      
        try: 
            self.write_response(filepath)
        except socket.error, e:     
            if isinstance(e.args, tuple):
                if e[0] in (errno.EPIPE, errno.ECONNRESET):
                   print "disconnected"
                   self.suppress_socket_error_report = True
                   return
            
            raise


    def handle_one_request(self):
        try:
            return BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self)
        except socket.error:
            if not self.suppress_socket_error_report:
                raise


    def finish(self):
        try:
            return BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
        except socket.error:
            if not self.suppress_socket_error_report:
                raise


    def send_headers(self, filepath):
        self.protocol_version = "HTTP/1.1"
        self.send_response(200)
        self.send_header("Content-type", self.content_type)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header("Transfer-Encoding", "chunked")
        self.end_headers()    


    def write_response(self, filepath):
        with open(filepath, "rb") as f:           
            while True:
                line = f.read(1024)
                if len(line) == 0:
                    break
            
                chunk_size = "%0.2X" % len(line)
                self.wfile.write(chunk_size)
                self.wfile.write("\r\n")
                self.wfile.write(line) 
                self.wfile.write("\r\n")  
                
        self.wfile.write("0")
        self.wfile.write("\r\n\r\n")                             



class TranscodingRequestHandler(RequestHandler):
    """ Handle HTTP requests for files which require realtime transcoding with ffmpeg """
    transcoder_command = FFMPEG
    transcode_options = ""
    transcode_input_options = ""    
    bufsize = 0
                    
    def write_response(self, filepath):
        if self.bufsize != 0:
            print "transcode buffer size:", self.bufsize
        
        ffmpeg_command = self.transcoder_command % (self.transcode_input_options, filepath, self.transcode_options) 
        
        ffmpeg_process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, shell=True, bufsize=self.bufsize)       

        for line in ffmpeg_process.stdout:
            chunk_size = "%0.2X" % len(line)
            self.wfile.write(chunk_size)
            self.wfile.write("\r\n")
            self.wfile.write(line) 
            self.wfile.write("\r\n")            
            
        self.wfile.write("0")
        self.wfile.write("\r\n\r\n")



class SubRequestHandler(RequestHandler):
    """ Handle HTTP requests for subtitles files """
    content_type = "text/vtt;charset=utf-8"



            
def get_transcoder_cmds(preferred_transcoder=None):
    """ establish which transcoder utility to use depending on what is installed """
    probe_cmd = None
    transcoder_cmd = None
    
    ffmpeg_installed = is_transcoder_installed("ffmpeg")
    avconv_installed = is_transcoder_installed("avconv")  
    
    # if anything other than avconv is preferred, try to use ffmpeg otherwise use avconv    
    if preferred_transcoder != "avconv":
        if ffmpeg_installed:
            transcoder_cmd = "ffmpeg"
            probe_cmd = "ffprobe"
        elif avconv_installed:
            print "unable to find ffmpeg - using avconv"
            transcoder_cmd = "avconv"
            probe_cmd = "avprobe"
    
    # otherwise, avconv is preferred, so try to use avconv, followed by ffmpeg  
    else:
        if avconv_installed:
            transcoder_cmd = "avconv"
            probe_cmd = "avprobe"
        elif ffmpeg_installed:
            print "unable to find avconv - using ffmpeg"
            transcoder_cmd = "ffmpeg"
            probe_cmd = "ffprobe"
            
    return transcoder_cmd, probe_cmd
    
    
                

def is_transcoder_installed(transcoder_application):
    """ check for an installation of either ffmpeg or avconv """
    try:
        subprocess.check_output([transcoder_application, "-version"])
        return True
    except OSError:
        return False
       



def kill_old_pid(device_ip):
    """ attempts to kill a previously running instance of this application casting to the specified device. """
    pid_file = PIDFILE % device_ip
    try:
        with open(pid_file, "r") as pidfile:
            pid = int(pidfile.read())
            os.killpg(pid, signal.SIGTERM)    
    except:
        pass
               


def save_pid(device_ip):
    """ saves the process id of this application casting to the specified device in a pid file. """
    pid_file = PIDFILE % device_ip
    with open(pid_file, "w") as pidfile:
        pidfile.write("%d" %  os.getpid())




def get_mimetype(filename, ffprobe_cmd=None):
    """ find the container format of the file """
    # default value
    mimetype = "video/mp4"
    
    
    # guess based on filename extension
    guess = mimetypes.guess_type(filename)[0]
    if guess is not None:
        if guess.lower().startswith("video/") or guess.lower().startswith("audio/"):
            mimetype = guess
      
        
    # use the OS file command...
    try:
        file_cmd = 'file --mime-type -b "%s"' % filename
        file_mimetype = subprocess.check_output(file_cmd, shell=True).strip().lower()
        
        if file_mimetype.startswith("video/") or file_mimetype.startswith("audio/"):
            mimetype = file_mimetype
            
            print "OS identifies the mimetype as :", mimetype
            return mimetype
    except:
        pass
    
    
    # use ffmpeg/avconv if installed
    if ffprobe_cmd is None:
        return mimetype
    
    # ffmpeg/avconv is installed
    has_video = False
    has_audio = False
    format_name = None
    
    ffprobe_cmd = '%s -show_streams -show_format "%s"' % (ffprobe_cmd, filename)
    ffmpeg_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, shell=True)

    for line in ffmpeg_process.stdout:
        if line.startswith("codec_type=audio"):
            has_audio = True
        elif line.startswith("codec_type=video"):
            has_video = True    
        elif line.startswith("format_name="):
            name, value = line.split("=")
            format_name = value.strip().lower().split(",")


    # use the default if it isn't possible to identify the format type
    if format_name is None:
        return mimetype
    
    
    if has_video:
        mimetype = "video/"
    else:
        mimetype = "audio/"
        
    if "mp4" in format_name:
        mimetype += "mp4"            
    elif "webm" in format_name:
        mimetype += "webm"
    elif "ogg" in format_name:
        mimetype += "ogg"        
    elif "mp3" in format_name:
        mimetype = "audio/mpeg"
    elif "wav" in format_name:
        mimetype = "audio/wav" 
    else:   
        mimetype += "mp4"     
        
    return mimetype
    
            
            
def play(filename, transcode=False, transcoder=None, transcode_options=None, transcode_input_options=None,
         transcode_bufsize=0, device_name=None, server_ip=None, server_port=None, server_external_port=None,
         subtitles=None, subtitles_port=None, subtitles_language=None):
    """ play a local file or transcode from a file or URL and stream to the chromecast """
    
    print_ident()
    
    
    cast = CCMediaController(device_name=device_name)
    
    kill_old_pid(cast.host)
    save_pid(cast.host)    


    if os.path.isfile(filename):
        filename = os.path.abspath(filename)
        print "source is file: %s" % filename
    else:
        if transcode and (filename.lower().startswith("http://") or filename.lower().startswith("https://") or filename.lower().startswith("rtsp://")):
            print "source is URL: %s" % filename
        else: 
            sys.exit("media file %s not found" % filename)
        

    
    transcoder_cmd, probe_cmd = get_transcoder_cmds(preferred_transcoder=transcoder)
    

    status = cast.get_status()
    webserver_ip = status['client'][0]
    
    if server_ip is None:
	server_ip = webserver_ip
    
    print "local ip address:", webserver_ip
        
    
    req_handler = RequestHandler
    
    if transcode:
        if transcoder_cmd in ("ffmpeg", "avconv"):
            req_handler = TranscodingRequestHandler
            
            if transcoder_cmd == "ffmpeg":  
                req_handler.transcoder_command = FFMPEG
            else:
                req_handler.transcoder_command = AVCONV
                
            if transcode_options is not None:    
                req_handler.transcode_options = transcode_options
                
            if transcode_input_options is not None:    
                req_handler.transcode_input_options = transcode_input_options                
                
            req_handler.bufsize = transcode_bufsize
        else:
            print "No transcoder is installed. Attempting standard playback"
   
    
    
    if req_handler == RequestHandler:
        req_handler.content_type = get_mimetype(filename, probe_cmd)
        
    
    # create a webserver to handle a single request for the media file on either a free port or on a specific port if passed in the port parameter   
    port = 0    
    
    if server_port is not None:
        port = int(server_port)
        
    server = BaseHTTPServer.HTTPServer((webserver_ip, port), req_handler)
    
    if server_external_port is None:
    	server_external_port = server.server_port

    thread = Thread(target=server.handle_request)
    thread.start()


    url = "http://%s:%s?%s" % (server_ip, str(server_external_port), urllib.quote_plus(filename, "/"))

    print "URL & content-type: ", url, req_handler.content_type


    # create another webserver to handle a request for the subtitles file, if specified in the subtitles parameter
    sub = None

    if subtitles:
        if os.path.isfile(subtitles):
            sub_port = 0

            if subtitles_port is not None:
                sub_port = int(subtitles_port)

            sub_server = BaseHTTPServer.HTTPServer((webserver_ip, sub_port), SubRequestHandler)
            thread2 = Thread(target=sub_server.handle_request)
            thread2.start()

            sub = "http://%s:%s?%s" % (webserver_ip, str(sub_server.server_port), urllib.quote_plus(subtitles, "/"))
            print "sub URL: ", sub
        else:
            print "Subtitles file %s not found" % subtitles


    load(cast, url, req_handler.content_type, sub, subtitles_language)

    
    

def load(cast, url, mimetype, sub=None, sub_language=None):
    """ load a chromecast instance with a url and wait for idle state """
    try:
        print "loading media..."
        
        cast.load(url, mimetype, sub, sub_language)
        
        # wait for playback to complete before exiting
        print "waiting for player to finish - press ctrl-c to stop..."    
        
        idle = False
        while not idle:
            time.sleep(1)
            idle = cast.is_idle()
   
    except KeyboardInterrupt:
        print
        print "stopping..."
        cast.stop()
        
    finally:
        print "done"
    
    
    
def playurl(url, device_name=None):
    """ play a remote HTTP resource on the chromecast """
    
    print_ident()

    def get_resp(url):
        url_parsed = urlparse.urlparse(url)
    
        scheme = url_parsed.scheme
        host = url_parsed.netloc
        path = url.split(host, 1)[-1]
        
        conn = None
        if scheme == "https":
            conn = httplib.HTTPSConnection(host)
        else:
            conn = httplib.HTTPConnection(host)
        
        conn.request("HEAD", path)
    
        resp = conn.getresponse()
        return resp


    def get_full_url(url, location):
        url_parsed = urlparse.urlparse(url)

        scheme = url_parsed.scheme
        host = url_parsed.netloc

        if location.startswith("/") is False:
            path = url.split(host, 1)[-1] 
            if path.endswith("/"):
                path = path.rsplit("/", 2)[0]
            else:
                path = path.rsplit("/", 1)[0] + "/"
            location = path + location

        full_url = scheme + "://" + host + location

        return full_url


    resp = get_resp(url)

    if resp.status != 200:
        redirect_codes = [ 301, 302, 303, 307, 308 ]
        if resp.status in redirect_codes:
            redirects = 0
            while resp.status in redirect_codes:
                redirects += 1
                if redirects > 9:
                    sys.exit("HTTP Error: Too many redirects")
                headers = resp.getheaders()
                for header in headers:
                    if len(header) > 1:
                        if header[0].lower() == "location":
                            redirect_location = header[1]
                if redirect_location.startswith("http") is False:
                    redirect_location = get_full_url(url, redirect_location)
                print "Redirecting to " + redirect_location
                resp = get_resp(redirect_location)
            if resp.status != 200:
                sys.exit("HTTP error:" + str(resp.status) + " - " + resp.reason)
        else:
            sys.exit("HTTP error:" + str(resp.status) + " - " + resp.reason)
        
    print "Found HTTP resource"
    
    headers = resp.getheaders()
    
    mimetype = None
    
    for header in headers:
        if len(header) > 1:
            if header[0].lower() == "content-type":
                mimetype = header[1]
    
    if mimetype != None:            
        print "content-type:", mimetype
    else:
        mimetype = "video/mp4"
        print "resource does not specify mimetype - using default:", mimetype
    
    cast = CCMediaController(device_name=device_name)
    load(cast, url, mimetype)    
    

            
    
def pause(device_name=None):
    """ pause playback """
    CCMediaController(device_name=device_name).pause()


def unpause(device_name=None):
    """ continue playback """
    CCMediaController(device_name=device_name).play()    

        
def stop(device_name=None):
    """ stop playback and quit the media player app on the chromecast """
    CCMediaController(device_name=device_name).stop()

def get_volume(device_name=None):
    """ print the status of the chromecast device """
    print CCMediaController(device_name=device_name).get_volume()

def get_status(device_name=None):
    """ print the status of the chromecast device """
    print CCMediaController(device_name=device_name).get_status()

def volume_up(device_name=None):
    """ raise the volume by 0.1 """
    CCMediaController(device_name=device_name).set_volume_up()


def volume_down(device_name=None):
    """ lower the volume by 0.1 """
    CCMediaController(device_name=device_name).set_volume_down()


def set_volume(v, device_name=None):
    """ set the volume to level between 0 and 1 """
    CCMediaController(device_name=device_name).set_volume(v)
    
    
def list_devices():
    print "Searching for devices, please wait..."
    device_ips = cc_device_finder.search_network(device_limit=None, time_limit=10)
    
    print "%d devices found" % len(device_ips)
    
    for device_ip in device_ips:
        print device_ip, ":", cc_device_finder.get_device_name(device_ip)
        

def print_ident():
    """ display initial messages """
    print
    print "-----------------------------------------"   
    print     
    print "Stream2Chromecast version:%s" % VERSION        
    print 
    print "Copyright (C) 2014-2016 Pat Carter"
    print "GNU General Public License v3.0" 
    print "https://www.gnu.org/licenses/gpl-3.0.html"
    print    
    print "-----------------------------------------"
    print 
    

def validate_args(args):
    """ validate that there are the correct number of arguments """
    if len(args) < 1:
        sys.exit(USAGETEXT)
        
    if args[0] == "-setvol" and len(args) < 2:
        sys.exit(USAGETEXT) 
    


def get_named_arg_value(arg_name, args, integer=False):
    """ get a argument value by name """
    arg_val = None
    if arg_name in args:

        arg_pos = args.index(arg_name)
        arg_name = args.pop(arg_pos)
        
        if len(args) > (arg_pos + 1):
            arg_val = args.pop(arg_pos)
    
    if integer:
        int_arg_val = 0
        if arg_val is not None:
            try:
                int_arg_val = int(arg_val)
            except ValueError:
                print "Invalid integer parameter, defaulting to zero. Parameter name:", arg_name
                
        arg_val = int_arg_val
                
    return arg_val
    
        

def run():
    """ main execution """
    args = sys.argv[1:]
    
    
    # optional device name parm. if not specified, device_name = None (the first device found will be used).
    device_name = get_named_arg_value("-devicename", args)
    
    # optional transcoder parm. if not specified, ffmpeg will be used, if installed, otherwise avconv.
    transcoder = get_named_arg_value("-transcoder", args)

    server_ip = get_named_arg_value("-server_ip", args) 
    
    # optional server port parm. if not specified, a random available port will be used
    server_port = get_named_arg_value("-port", args)

    server_external_port = get_named_arg_value("-external_port", args)     
    
    # optional transcode options parm. if specified, these options will be passed to the transcoder to be applied to the output
    transcode_options = get_named_arg_value("-transcodeopts", args)     
    
    # optional transcode options parm. if specified, these options will be passed to the transcoder to be applied to the input data
    transcode_input_options = get_named_arg_value("-transcodeinputopts", args)      
    
    # optional transcode bufsize parm. if specified, the transcoder will buffer approximately this many bytes of output
    transcode_bufsize = get_named_arg_value("-transcodebufsize", args, integer=True)

    # optional subtitle parm. if specified, the specified subtitles will be played.
    subtitles = get_named_arg_value("-subtitles", args)

    # optional subtitle_port parm. if not specified, a random available port will be used.
    subtitles_port = get_named_arg_value("-subtitles_port", args)

    # optional subtitle_language parm. if not specified en-US will be used.
    subtitles_language = get_named_arg_value("-subtitles_language", args)


        
    validate_args(args)
    
    if args[0] == "-stop":
        stop(device_name=device_name)
        
    elif args[0] == "-pause":
        pause(device_name=device_name)        
    
    elif args[0] == "-continue":
        unpause(device_name=device_name)           
    
    elif args[0] == "-status":
        get_status(device_name=device_name)

    elif args[0] == "-getvol":
        get_volume(device_name=device_name)

    elif args[0] == "-setvol":
        set_volume(float(args[1]), device_name=device_name)

    elif args[0] == "-volup":
        volume_up(device_name=device_name)

    elif args[0] == "-voldown":
        volume_down(device_name=device_name)

    elif args[0] == "-mute":
        set_volume(0, device_name=device_name)

    elif args[0] == "-transcode":    
        arg2 = args[1]  
        play(arg2, transcode=True, transcoder=transcoder, transcode_options=transcode_options, transcode_input_options=transcode_input_options, transcode_bufsize=transcode_bufsize,
             device_name=device_name, server_ip=server_ip, server_port=server_port, server_external_port=server_external_port, subtitles=subtitles, subtitles_port=subtitles_port,
             subtitles_language=subtitles_language)
        
    elif args[0] == "-playurl":    
        arg2 = args[1]  
        playurl(arg2, device_name=device_name)                          
        
    elif args[0] == "-devicelist":
        list_devices()
            
    else:
        play(args[0], device_name=device_name, server_ip=server_ip, server_port=server_port, server_external_port=server_external_port, subtitles=subtitles,
             subtitles_port=subtitles_port, subtitles_language=subtitles_language)
        
            
if __name__ == "__main__":
    run()

cc_media_controller.py
Spoiler: show

Code: Select all

"""
Provides a control interface to the Chromecast Media Player app

version 0.2.1

"""


# Copyright (C) 2014-2016 Pat Carter
#
# This file is part of Stream2chromecast.
#
# Stream2chromecast is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Stream2chromecast is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Stream2chromecast.  If not, see <http://www.gnu.org/licenses/>.



import socket, ssl, select
import json
import sys
import time
import re

import cc_device_finder
import cc_message


MEDIAPLAYER_APPID = "CC1AD845"
 

class CCMediaController():
    def __init__(self, device_name=None):
        """ initialise """
        
        self.host = self.get_device(device_name)

        self.sock = None
        
        self.request_id = 1
        self.source_id = "sender-0"

        self.receiver_app_status = None
        self.media_status = None
        self.volume_status = None
        self.current_applications = None
        
    
    
    def get_device(self, device_name):
        """ get the device ip address """
        
        host = None
        dummy = None

        is_ip_addr = device_name is not None and re.match( "[0-9]+.[0-9]+.[0-9]+.[0-9]+$", device_name) is not None
        
        if is_ip_addr:
            host = device_name
            try:
                #print "ip_addr:", host.encode('utf-8'), "device name:", cc_device_finder.get_device_name(host).encode('utf-8')
                print >> sys.stderr, "ip_addr:", host.encode('utf-8'), "device name:", cc_device_finder.get_device_name(host).encode('utf-8')

            except socket.error:
                sys.exit("No Chromecast found on ip:" + host)
        else:
            host, name = cc_device_finder.find_device(name=device_name)
            if host is None:
                sys.exit("No Chromecast found on the network")
                
            #print "device name:", name    
            print >> sys.stderr, "device name:", name 
        return host
        
        
        
    def open_socket(self):
        """ open a socket if there is not currently one open """
        
        if self.sock is None:
            self.sock = socket.socket()
            self.sock = ssl.wrap_socket(self.sock)

            self.sock.connect((self.host,8009))

                
    def close_socket(self):
        """ close the socket if there is one open """
        
        if self.sock is not None:
            self.sock.close()
            
        self.sock = None



    def send_data(self, namespace, data_dict):
        """ send data to the device in binary format"""
        
        data = json.dumps(data_dict)
        
        #print "Sending: ", namespace, data
        
        msg = cc_message.format_message(self.source_id, self.destination_id, namespace, data)
        
        self.sock.write(msg)

        
        
    def read_message(self):
        """ read a complete message from the device """

        data = ""
        while len(data) < 4:
            data += self.sock.recv(4)
        
        msg_length, data = cc_message.extract_length_header(data) 
        while len(data) < msg_length:
            data += self.sock.recv(2048)
            
       
        message_dict = cc_message.extract_message(data)
        
        message = {}
        
        try:
            message = json.loads(message_dict['data'])
        except:
            pass
        
        #print message_dict['namespace']
        #print json.dumps(message, indent=4, separators=(',', ': '))
        
        return message   
        
         
    
    def get_response(self, request_id):
        """ get the response matching the original request id """
        
        resp = {}
        
        count = 0
        while len(resp) == 0:
            msg = self.read_message()
            
            msg_type = msg.get("type", msg.get("responseType", ""))
            
            if msg_type == "PING":
                data = {"type":"PONG"}
                namespace = "urn:x-cast:com.google.cast.tp.heartbeat"
                self.send_data(namespace, data) 
                
                # if 30 ping/pong messages are received without a response to the request_id, 
                # assume no response is coming
                count += 1
                if count == 30:
                    return resp
                
            elif msg_type == "RECEIVER_STATUS":
                self.update_receiver_status_data(msg)
                
            elif msg_type == "MEDIA_STATUS":
                self.update_media_status_data(msg)
            
            if "requestId" in msg.keys() and msg['requestId'] == request_id:
                resp = msg
                
        return resp



    def send_msg_with_response(self, namespace, data):
        """ send a request to the device and wait for a response matching the request id """
        
        self.request_id += 1
        data['requestId'] = self.request_id
        
        self.send_data(namespace, data)
        
        return self.get_response(self.request_id)

            
        
    def update_receiver_status_data(self, msg):
        """ update the status for the Media Player app if it is running """
        
        self.receiver_app_status = None
        
        if msg.has_key('status'):
            status = msg['status']
            if status.has_key('applications'):
                self.current_applications = status['applications']
                for application in self.current_applications:
                    if application.get("appId") == MEDIAPLAYER_APPID:
                        self.receiver_app_status = application
                        
                        
            if status.has_key('volume'):
                self.volume_status = status['volume']
                        
                        
                        
    def update_media_status_data(self, msg): 
        """ update the media status if there is any media loaded """
        
        self.media_status = None
        
        status = msg.get("status", [])
        if len(status) > 0:  
            self.media_status = status[0] # status is an array - selecting the first result..?                 


         
        
    def connect(self, destination_id):  
        """ connect to to the receiver or the media transport """
        
        if self.sock is None:
            self.open_socket()
                     
        self.destination_id = destination_id
        
        data = {"type":"CONNECT","origin":{}}
        namespace = "urn:x-cast:com.google.cast.tp.connection"
        self.send_data(namespace, data)
        
        
    
    def get_receiver_status(self):
        """ send a status request to the receiver """
        
        data = {"type":"GET_STATUS"}
        namespace = "urn:x-cast:com.google.cast.receiver"
        self.send_msg_with_response(namespace, data)
                
    
    
    def get_media_status(self):
        """ send a status request to the media player """
        
        data = {"type":"GET_STATUS"}
        namespace = "urn:x-cast:com.google.cast.media"
        self.send_msg_with_response(namespace, data)   
            
            
                    
    def load(self, content_url, content_type, sub, sub_language):
        """ Launch the player app, load & play a URL """
        
        self.connect("receiver-0")

        self.get_receiver_status()
        
        # we only set the receiver status for MEDIAPLAYER - so if it is set, the app is currenty running
        if self.receiver_app_status is None:
            data = {"type":"LAUNCH","appId":MEDIAPLAYER_APPID}
            namespace = "urn:x-cast:com.google.cast.receiver"
            self.send_msg_with_response(namespace, data)
            
            # if there is still no receiver app status the launch failed.
            if self.receiver_app_status is None:
                self.close_socket()
                sys.exit("Cannot launch the Media Player app")
                
        
        session_id = str(self.receiver_app_status['sessionId'])
        transport_id = str(self.receiver_app_status['transportId'])

        self.connect(transport_id)

        data = {"type":"LOAD",
                "sessionId":session_id,
                "media":{
                    "contentId":content_url,
                    "streamType":"buffered",
                    "contentType":content_type,
                    },
                "autoplay":True,
                "currentTime":0,
                "customData":{
                    "payload":{
                        "title:":""
                        }
                    }
                }


        if sub:        
            if sub_language is None:
                sub_language = "en-US"
                
            data["media"].update({
                                "textTrackStyle":{
                                    'backgroundColor':'#FFFFFF00'
                                },
                                "tracks": [{"trackId": 1,
                                            "trackContentId": sub,
                                            "type": "TEXT",
                                            "language": sub_language,
                                            "subtype": "SUBTITLES",
                                            "name": "Englishx",
                                            "trackContentType": "text/vtt",
                                           }],
                                })
            data["activeTrackIds"] = [1]

        
        namespace = "urn:x-cast:com.google.cast.media"
        resp = self.send_msg_with_response(namespace, data)


        # wait for the player to return "BUFFERING", "PLAYING" or "IDLE"
        if resp.get("type", "") == "MEDIA_STATUS":            
            player_state = ""
            while player_state != "PLAYING" and player_state != "IDLE" and player_state != "BUFFERING":
                time.sleep(2)        
                
                self.get_media_status()
                
                if self.media_status != None:
                    player_state = self.media_status.get("playerState", "")

                
        self.close_socket()       


            
    def control(self, command, parameters={}):      
        """ send a control command to the player """
          
        self.connect("receiver-0")

        self.get_receiver_status()
        
        if self.receiver_app_status is None:
            print >> sys.stderr, "No media player app running"
            self.close_socket()
            return      
        
        transport_id = str(self.receiver_app_status['transportId'])       
        
        self.connect(transport_id)
        
        self.get_media_status()
        
        media_session_id = 1
        if self.media_status is not None:
            media_session_id = self.media_status['mediaSessionId']
                                                                     
        data = {"type":command, "mediaSessionId":media_session_id}
        data.update(parameters)  # for additional parameters
        
        namespace = "urn:x-cast:com.google.cast.media"
        self.send_msg_with_response(namespace, data)
        
        self.close_socket()
                       
    
    
    def get_status(self):
        """ get the receiver and media status """
        
        self.connect("receiver-0")

        self.get_receiver_status()
        
        if self.receiver_app_status is not None:   
        	transport_id = str(self.receiver_app_status['transportId']) 
        	self.connect(transport_id)
        	self.get_media_status()
        
        application_list = []
        if self.current_applications is not None:
            for application in self.current_applications:
                application_list.append({
                    'appId':application.get('appId', ""), 
                    'displayName':application.get('displayName', ""),  
                    'statusText':application.get('statusText', "")})
        
        status = {'receiver_status':self.receiver_app_status, 
                  'media_status':self.media_status, 
                  'host':self.host, 
                  'client':self.sock.getsockname(),
                  'applications':application_list}
                
        self.close_socket()
        
        return status
        
        
        
#    def is_idle(self):
#        """ return the IDLE state of the player """
        
#        status = self.get_status()
        
#        if status['media_status']  is None:
#            if status['receiver_status'] is None:
#                return True
#            else:    
#                return status['receiver_status'].get("statusText", "") == u"Ready To Cast"

#        else:    
#            return status['media_status'].get("playerState", "") == u"IDLE"

# https://www.domoticz.com/forum/viewtopic.php?f=69&t=22610&view=unread&sid=ad3e2da7f9d4eee231d26e7e63558869#unread
# Added 16-09-2918

    def is_idle(self):
        """ return the IDLE state of the player """
        
        status = self.get_status()
        
        if status['media_status']  is None:
                return True
        else:    
            return status['media_status'].get("playerState", "") == u"IDLE"

       
       

    def pause(self):
        """ pause """
        self.control("PAUSE") 
            
    def play(self):
        """ unpause """
        self.control("PLAY")   
        
    def stop(self):
        """ stop """
        self.control("STOP")
        self.connect("receiver-0")

        data = {"type":"STOP"}
        namespace = "urn:x-cast:com.google.cast.receiver"
        self.send_msg_with_response(namespace, data)  
        
        self.close_socket() 
         
        
        
        
    def set_volume(self, level):
        """ set the receiver volume - a float value in level for absolute level or "+" / "-" indicates up or down"""
        
        self.connect("receiver-0")

        if level in ("+", "-"):
            self.get_receiver_status()
        
            if self.volume_status is not None:
                curr_level = self.volume_status['level']
                if level == "+":
                    level = 0.1 + curr_level
                elif level == "-":
                    level = curr_level - 0.1
            
        
        data = {"type":"SET_VOLUME", "volume":{"muted":False, "level":level} }
        namespace = "urn:x-cast:com.google.cast.receiver"
        self.send_msg_with_response(namespace, data)  
        
        self.close_socket() 
        
        
    def get_volume_ext(self):
        """ get the current volume level """

        self.get_status()
        
        vol = None
        
        if self.volume_status is not None:
            vol = self.volume_status.get('level', None)
                
        return vol
    
    def get_volume(self):
        """ get the current volume level """
        self.get_status()
        
        vol = None
        
        if self.volume_status is not None:
            vol = self.volume_status.get('level', None)
                
        return vol
                         
                              
                                
    def set_volume_up(self):
        """ increase volume by one step """
        self.set_volume("+")
            

            
    def set_volume_down(self):
        """ decrease volume by one step """
        self.set_volume("-")    
            
okay almost there , but getting :

Code: Select all

Notification : There is someone at the door
Night volume
Volume = automatique - set to 0.2
ip_addr: 192.168.1.46 device name: Google home hub

-----------------------------------------

Stream2Chromecast version:0.6.3

Copyright (C) 2014-2016 Pat Carter
GNU General Public License v3.0
https://www.gnu.org/licenses/gpl-3.0.html

-----------------------------------------

ip_addr: 192.168.1.46 device name: Google home hub
Traceback (most recent call last):
  File "/home/pim/stream2chromecast/stream2chromecast.py", line 815, in <module>
    run()
  File "/home/pim/stream2chromecast/stream2chromecast.py", line 811, in run
    subtitles_port=subtitles_port, subtitles_language=subtitles_language)
  File "/home/pim/stream2chromecast/stream2chromecast.py", line 419, in play
    save_pid(cast.host)
  File "/home/pim/stream2chromecast/stream2chromecast.py", line 326, in save_pid
    with open(pid_file, "w") as pidfile:
IOError: [Errno 2] No such file or directory: '/tmp/google/stream2chromecast_192.168.1.46.pid'
So what should be the way to get this final things done :mrgreen:
It's nice to be important, but it's more important to be nice
Scooter ;)
User avatar
sincze
Posts: 1300
Joined: Monday 02 June 2014 22:46
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.4
Location: Netherlands / Breda Area
Contact:

Re: notifications to chromecast

Post by sincze »

Does /tmp/google directory exist with necessary permissions?
Pass2php
LAN: RFLink, P1, OTGW, MySensors
USB: RFXCom, ZWave, Sonoff 3
MQTT: ZIgbee2MQTT,
ZWAVE: Zwave-JS-UI
WIFI: Mi-light, Tasmota, Xiaomi Shelly
Solar: Omnik, PVOutput
Video: Kodi, Harmony HUB, Chromecast
Sensors: You name it I got 1.
User avatar
sincze
Posts: 1300
Joined: Monday 02 June 2014 22:46
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.4
Location: Netherlands / Breda Area
Contact:

Re: notifications to chromecast

Post by sincze »

Does /tmp/google directory exist with necessary permissions?
Pass2php
LAN: RFLink, P1, OTGW, MySensors
USB: RFXCom, ZWave, Sonoff 3
MQTT: ZIgbee2MQTT,
ZWAVE: Zwave-JS-UI
WIFI: Mi-light, Tasmota, Xiaomi Shelly
Solar: Omnik, PVOutput
Video: Kodi, Harmony HUB, Chromecast
Sensors: You name it I got 1.
User avatar
sincze
Posts: 1300
Joined: Monday 02 June 2014 22:46
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.4
Location: Netherlands / Breda Area
Contact:

Re: notifications to chromecast

Post by sincze »

sincze wrote: Sunday 23 September 2018 19:59 I thought... I had a good idea to move all my IOT stuff to a seperate VLAN.
My Google Chromecasts, Google Home , LG Smart TV's you name it.

After some modifications it all works.
Domoticz can reach the devices and control them, read the status.
... However... what is not working.. Stream2chromecast.

Code: Select all

-----------------------------------------

Stream2Chromecast version:0.6.3

Copyright (C) 2014-2016 Pat Carter
GNU General Public License v3.0
https://www.gnu.org/licenses/gpl-3.0.html

-----------------------------------------

ip_addr: 192.168.yy.yy device name:
source is file: /tmp/google/2f55628619d7d0366ece864b2cdf67d8.mp3
local ip address: 192.168.xx.xx
OS identifies the mimetype as : audio/mpeg
URL & content-type:  http://192.168.xx.xx:39121?/tmp/google/2f55628619d7d0366ece864b2cdf67d8.mp3 audio/mpeg
loading media...
I opened op the router VLAN Firewall (TCP & UDP) so the google home can reach the Domoticz machine where stream2chromecast is running from. However it seems it is unable to collect the file. It does adjust the playback volume to the correct level.
Where 192.168.yy.yy is the Google Home and 192.168.xx.xx is the Domoticz machine.

Code: Select all

user@raspberry-user:~ $ sudo iptables --list
iptables v1.6.0: can't initialize iptables table `filter': Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.
What did I miss :lol:
Fixed my own issue today with help of https://community.ubnt.com/t5/UniFi-Rou ... -p/2184761. Just allowed IOT VLAN traffic to my domoticz hosts machine on a pre-defined port. The pre-defined port is described in the wiki of stream2chromecast.

So a command on the Domoticz server machine:

Code: Select all

 sudo ./stream2chromecast.py -port 8765 -devicename 192.168.yy.yy /tmp/google/7688df2479ece342b15cab525876d
9b5.mp3
Will have the following result:

Code: Select all

-----------------------------------------

Stream2Chromecast version:0.6.3

Copyright (C) 2014-2016 Pat Carter
GNU General Public License v3.0
https://www.gnu.org/licenses/gpl-3.0.html

-----------------------------------------

ip_addr: 192.168.yy.yy device name:
source is file: /tmp/google/7688df2479ece342b15cab525876d9b5.mp3
local ip address: 192.168.xx.xx
OS identifies the mimetype as : audio/mpeg
URL & content-type:  http://192.168.xx.xx:8765?/tmp/google/7688df2479ece342b15cab525876d9b5.mp3 audio/mpeg
loading media...
192.168.yy.yy - - [03/Jan/2019 20:17:34] "GET /?/tmp/google/7688df2479ece342b15cab525876d9b5.mp3 HTTP/1.1" 200 -
sending data
waiting for player to finish - press ctrl-c to stop...
done
So all is working now with all IOT in its own VLAN.
Pass2php
LAN: RFLink, P1, OTGW, MySensors
USB: RFXCom, ZWave, Sonoff 3
MQTT: ZIgbee2MQTT,
ZWAVE: Zwave-JS-UI
WIFI: Mi-light, Tasmota, Xiaomi Shelly
Solar: Omnik, PVOutput
Video: Kodi, Harmony HUB, Chromecast
Sensors: You name it I got 1.
RvD
Posts: 8
Joined: Wednesday 15 October 2014 18:54
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: notifications to chromecast

Post by RvD »

glsf91 wrote: Saturday 22 September 2018 14:42
mark.sellwood wrote: Wednesday 19 September 2018 22:13 Hi have made the change to cc_media_controller.py but still it hangs after the message sending data. I do hear the message.

Any suggestions?
I also have sometime a hanging script. So I switched to pychromecast.
Warning: the programming below is maybe awfull but it is working (I'am not a programmer).

I use this with https://www.domoticz.com/forum/viewtopi ... 10#p175077.

Added a script play_chromecast.py:


Of course adjust the path of the scripts and domoticz url in the last script to yours.
I think this works better but I did not test for a long time.

You need python 3.4+ (check with command: python3)
Install pychromecast with:
apt-get install python3-pip (if you don't have python3 pip)
python3 -m pip install pychromecast --user

<apikey> wel aanpassen
Thanks for your work, this works just fine!
I have two suggestions:

Suggestion 1: Making it faster
The script might take some time to start, because it searches all Google Homes for the correct one. Since I have 6 of them, this takes some time. That's not an issue if you want to announce some general message, but it is an issue when used with a doorbell. The script takes about 5 seconds to initialize and search all Google Homes, and if you want to play this sound on multiple Homes when there's someone at the door, that takes too long. Also I have made a specific mp3-doorbell file that is stored permanently in the /www/geluiden-folder of Domoticz, so it doesn't go through Google TTS/Sox/etc etc. making it even faster.

I have modified the calling notification_google_home.sh as follows:
1) Edit the call to Python at the end, so the IPGH you entered earlier in the script is passed on:

Code: Select all

python3 /home/pi/stream2chromecast/play_chromecast.py http://192.168.1.110:8080/message.mp3 $volume $IPGH
2) Make the corresponding changes to play_chromecast.py

Code: Select all

messageUrl=args[0]
volume=float(args[1])
# This line is new
ip=args[2]

print("MessageUrl: ", messageUrl)
print("Volume: ", volume)
# This is new
print("Ip: ", ip)

# Comment out or delete these lines:
# Your Chromecast device Friendly Name
# device_friendly_name = "Zolder Speaker"
#chromecasts = pychromecast.get_chromecasts()
# select Chromecast device
#cast = next(cc for cc in chromecasts if cc.device.friendly_name == device_friendly_name)

# This line is new
cast = pychromecast.Chromecast(ip)
I have now created several .sh scripts: notify_livingroom.sh, notify_attic.sh and so on, each with the correct IP setup in the file. I can then call these scripts from domoticz and it works fine. There are other solutions possible (eg passing on the IP address from within domoticz), but this is easier. And also, if the IP address ever changes, you'll only need to edit the corresponding .sh file. Static IP Addresses / Reserved DHCP leases are required of course.

Suggestion 2: Releasing the player
In the file play_chromecast.py add this at the end:

Code: Select all

# poll so signal handlers still work
while not completion.wait(0.5):
        pass

cast.quit_app()
That final command will terminate the connection with the Chromecast/Google Home. Otherwise, you'll forever see in your Google Home app that a device is still playing something.
DutchHans
Posts: 229
Joined: Friday 03 April 2015 20:44
Target OS: Raspberry Pi / ODroid
Domoticz version:
Location: Germany (near dutch border)
Contact:

Re: notifications to chromecast

Post by DutchHans »

Hi, looks promising, can you give an example of your notify_livingroom.sh script?

Thank you in advance.
Hans
glsf91
Posts: 58
Joined: Tuesday 14 November 2017 21:56
Target OS: Linux
Domoticz version:
Contact:

Re: notifications to chromecast

Post by glsf91 »

RvD wrote: Sunday 13 January 2019 13:59
Suggestion 1: Making it faster

Suggestion 2: Releasing the player
Thanks for your improvements.
I also made some improvements and included yours.

Now the volume setting is restored to the previous setting.
Is something was already playing, strange effects are sometimes occurring.
Sometimes playing was skipped.

The complete play_chromecast.py script:

Code: Select all

from __future__ import print_function
import time
import pychromecast
import threading
from time import sleep
import sys


args = sys.argv[1:]

if len(args) < 3:
    sys.exit("Usage: notification_google_home <text> <volume 0..1> <IP address>")

messageUrl=args[0]
volume=float(args[1])
ip=args[2]

print("MessageUrl: ", messageUrl)
print("New volume: ", volume)
print("Ip chromecast: ", ip)


# Your Chromecast device Friendly Name
#device_friendly_name = "Family room speaker"
#device_friendly_name = "Google mini"

#chromecasts = pychromecast.get_chromecasts()

# select Chromecast device
#cast = next(cc for cc in chromecasts if cc.device.friendly_name == device_friendly_name)
cast = pychromecast.Chromecast(ip)

# wait for the device
cast.wait()
sleep(1)
#print(cast.device)
#print(cast.status)

# save current volume level
prev_volume_level = cast.status.volume_level
print('Prev volume: ',prev_volume_level)

# get media controller
mc = cast.media_controller
sleep(0.1)
#print('Mediastatus: ', mc.status)
print('PLaying?: ', mc.status.player_is_playing)
if (mc.status.player_is_playing ):
        mc.stop()
        sleep(0.1)

cast.set_volume(volume)
sleep(0.1)
#print('After set_volume: ', cast.status)

start_playing = False

completion = threading.Event()

class StatusMediaListener:
    def __init__(self, name, cast):
        self.name = name
        self.cast= cast

    def new_media_status(self, status):
        print('[',time.ctime(),' - ', self.name,'] status media change:')
        print(status)
        if (status.player_is_idle and start_playing):
             completion.set()

listenerMedia = StatusMediaListener(cast.name, cast)
cast.media_controller.register_status_listener(listenerMedia)


# set online video url
mc.play_media(messageUrl, 'audio/mp3')

# blocks device
mc.block_until_active()
#print('After block: ',mc.status)

# plays the video
mc.play()

sleep(0.5)
start_playing = True

# poll so signal handlers still work
while not completion.wait(0.5):
        pass


# Restore volume level
cast.set_volume(prev_volume_level)
#print(cast.status)
sleep(0.1)
#print(cast.status)
cast.quit_app()
Only tested on a google home mini.
RvD
Posts: 8
Joined: Wednesday 15 October 2014 18:54
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: notifications to chromecast

Post by RvD »

glsf91 wrote: Sunday 13 January 2019 16:35
Thanks for your improvements.
I also made some improvements and included yours.

Now the volume setting is restored to the previous setting.
Is something was already playing, strange effects are sometimes occurring.
Sometimes playing was skipped.

The complete play_chromecast.py script:

Code: Select all

from __future__ import print_function
import time
import pychromecast
import threading
from time import sleep
import sys


args = sys.argv[1:]

if len(args) < 3:
    sys.exit("Usage: notification_google_home <text> <volume 0..1> <IP address>")

messageUrl=args[0]
volume=float(args[1])
ip=args[2]

print("MessageUrl: ", messageUrl)
print("New volume: ", volume)
print("Ip chromecast: ", ip)


# Your Chromecast device Friendly Name
#device_friendly_name = "Family room speaker"
#device_friendly_name = "Google mini"

#chromecasts = pychromecast.get_chromecasts()

# select Chromecast device
#cast = next(cc for cc in chromecasts if cc.device.friendly_name == device_friendly_name)
cast = pychromecast.Chromecast(ip)

# wait for the device
cast.wait()
sleep(1)
#print(cast.device)
#print(cast.status)

# save current volume level
prev_volume_level = cast.status.volume_level
print('Prev volume: ',prev_volume_level)

# get media controller
mc = cast.media_controller
sleep(0.1)
#print('Mediastatus: ', mc.status)
print('PLaying?: ', mc.status.player_is_playing)
if (mc.status.player_is_playing ):
        mc.stop()
        sleep(0.1)

cast.set_volume(volume)
sleep(0.1)
#print('After set_volume: ', cast.status)

start_playing = False

completion = threading.Event()

class StatusMediaListener:
    def __init__(self, name, cast):
        self.name = name
        self.cast= cast

    def new_media_status(self, status):
        print('[',time.ctime(),' - ', self.name,'] status media change:')
        print(status)
        if (status.player_is_idle and start_playing):
             completion.set()

listenerMedia = StatusMediaListener(cast.name, cast)
cast.media_controller.register_status_listener(listenerMedia)


# set online video url
mc.play_media(messageUrl, 'audio/mp3')

# blocks device
mc.block_until_active()
#print('After block: ',mc.status)

# plays the video
mc.play()

sleep(0.5)
start_playing = True

# poll so signal handlers still work
while not completion.wait(0.5):
        pass


# Restore volume level
cast.set_volume(prev_volume_level)
#print(cast.status)
sleep(0.1)
#print(cast.status)
cast.quit_app()
Only tested on a google home mini.
Thanks for your work!
I also experienced the problem that when something else was already playing, the sound we are sending doesn't play.
You created an IF to check if something was playing and then issued a stop() command.
That doesn't seem to always work in my case.

I have found another solution, which always seems to work and doesn't even need any checking and doesn't cost any time either.
When you first create the CAST-object, just add this:

Code: Select all

cast = pychromecast.Chromecast(ip)
cast.quit_app()
It's a bit 'hackey' I admit, but it works perfectly.
Also, it doesn't destroy the CAST-object or anything, so no recreating it is neccessary.

Now all I need is a way to automatically resume playing what was played before, eg. a Spotify-song or a TuneIn Radio station...
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: notifications to chromecast

Post by rrozema »

In the comments of the topic in the french site another fork of stream2chromecast is mentioned that addresses both issues listed here:
  • stream2chromecast doesn't close after playback ends
  • volume is not returned to it's original value after playback ends
Additionally I found that it also fixes an issue I found very annoying myself:
  • stream2chromecast doesn't support device groups, only ip adresses are possible


Here's the link to the topic response by bricololo: https://easydomoticz.com/forum/viewtopi ... =80#p60075

And the github page for meute's fork of stream2chromecast: https://github.com/meute/stream2chromecast

So what I did to get the working version of stream2chromecast:

Code: Select all

cd ~
rm -r -f stream2chromecast
git clone https://github.com/meute/stream2chromecast
edit: the mDNS search only finds chromecasts, not home devices... So you still need to address the home(s) by their ip address.
edit 2: I haven't found how to employ the -getvol parameter to actually return the volume to it's original level, but at least meute mentions the possibility to do so.
glsf91
Posts: 58
Joined: Tuesday 14 November 2017 21:56
Target OS: Linux
Domoticz version:
Contact:

Re: notifications to chromecast

Post by glsf91 »

RvD wrote: Friday 18 January 2019 13:49 Now all I need is a way to automatically resume playing what was played before, eg. a Spotify-song or a TuneIn Radio station...
It is a long time ago but did you find a way to resume playing?
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests