Returns the first matching line in a list of lines . See match ()

def matching_line(lines, keyword):
    """Returns the first matching line in a list of lines. See match()"""
    for line in lines:
        matching=match(line,keyword)
        if matching!=None:
            return matching
    return None

def matching_line ( lines , keyword ) : for line in lines : matching = match ( line , keyword ) if matching != None : return matching return None


If the first part of line ( modulo blanks ) matches keyword returns the end of that line . Otherwise returns None

def match(line,keyword):
    """If the first part of line (modulo blanks) matches keyword,
    returns the end of that line. Otherwise returns None"""
    line=line.lstrip()
    length=len(keyword)
    if line[:length] == keyword:
        return line[length:]
    else:
        return None

def match ( line , keyword ) : line = line . lstrip ( ) length = len ( keyword ) if line [ : length ] == keyword : return line [ length : ] else : return None


def parse_cell(cell):
    """Applies the rules to the bunch of text describing a cell and returns the
    corresponding dictionary"""
    parsed_cell={}
    for key in rules:
        rule=rules[key]
        parsed_cell.update({key:rule(cell)})
    return parsed_cell

def parse_cell ( cell ) : parsed_cell = { } for key in rules : rule = rules [ key ] parsed_cell . update ( { key : rule ( cell ) } ) return parsed_cell


def start_app(host, app_id, data=None):
    """ Starts an application.

        If your TV is not on will turn it on unless app_id == APP_ID_HOME. """

    if data is None:
        data = {"": ""}

    CC_SESSION.post(_craft_app_url(host, app_id), data=data)

def start_app ( host , app_id , data = None ) : if data is None : data = { "" : "" } CC_SESSION . post ( _craft_app_url ( host , app_id ) , data = data )


Quits specified application if it is running . If no app_id specified will quit current running app .

def quit_app(host, app_id=None):
    """ Quits specified application if it is running.
        If no app_id specified will quit current running app. """

    if not app_id:
        status = get_app_status(host)

        if status:
            app_id = status.app_id

    if app_id:
        CC_SESSION.delete(_craft_app_url(host, app_id))

def quit_app ( host , app_id = None ) : if not app_id : status = get_app_status ( host ) if status : app_id = status . app_id if app_id : CC_SESSION . delete ( _craft_app_url ( host , app_id ) )


def reboot(host):
    """ Reboots the chromecast. """
    CC_SESSION.post(FORMAT_BASE_URL.format(host) + "/setup/reboot",
                    data='{"params":"now"}')

def reboot ( host ) : CC_SESSION . post ( FORMAT_BASE_URL . format ( host ) + "/setup/reboot" , data = '{"params":"now"}' )


Returns the device status as a named tuple .

def get_device_status(host):
    """ Returns the device status as a named tuple. """

    try:
        req = CC_SESSION.get(
            FORMAT_BASE_URL.format(host) + "/ssdp/device-desc.xml")

        status_el = ET.fromstring(req.text.encode("UTF-8"))

        device_info_el = status_el.find(XML_NS_UPNP_DEVICE + "device")
        api_version_el = status_el.find(XML_NS_UPNP_DEVICE + "specVersion")

        friendly_name = _read_xml_element(device_info_el, XML_NS_UPNP_DEVICE,
                                          "friendlyName", "Unknown Chromecast")
        model_name = _read_xml_element(device_info_el, XML_NS_UPNP_DEVICE,
                                       "modelName", "Unknown model name")
        manufacturer = _read_xml_element(device_info_el, XML_NS_UPNP_DEVICE,
                                         "manufacturer",
                                         "Unknown manufacturer")

        api_version = (int(_read_xml_element(api_version_el,
                                             XML_NS_UPNP_DEVICE, "major", -1)),
                       int(_read_xml_element(api_version_el,
                                             XML_NS_UPNP_DEVICE, "minor", -1)))

        return DeviceStatus(friendly_name, model_name, manufacturer,
                            api_version)

    except (requests.exceptions.RequestException, ET.ParseError):
        return None

def
get_device_status
(
host
)
:
try
:
req
=
CC_SESSION
.
get
(
FORMAT_BASE_URL
.
format
(
host
)
+
"/ssdp/device-desc.xml"
)
status_el
=
ET
.
fromstring
(
req
.
text
.
encode
(
"UTF-8"
)
)
device_info_el
=
status_el
.
find
(
XML_NS_UPNP_DEVICE
+
"device"
)
api_version_el
=
status_el
.
find
(
XML_NS_UPNP_DEVICE
+
"specVersion"
)
friendly_name
=
_read_xml_element
(
device_info_el
,
XML_NS_UPNP_DEVICE
,
"friendlyName"
,
"Unknown Chromecast"
)
model_name
=
_read_xml_element
(
device_info_el
,
XML_NS_UPNP_DEVICE
,
"modelName"
,
"Unknown model name"
)
manufacturer
=
_read_xml_element
(
device_info_el
,
XML_NS_UPNP_DEVICE
,
"manufacturer"
,
"Unknown manufacturer"
)
api_version
=
(
int
(
_read_xml_element
(
api_version_el
,
XML_NS_UPNP_DEVICE
,
"major"
,
1
)
)
,
int
(
_read_xml_element
(
api_version_el
,
XML_NS_UPNP_DEVICE
,
"minor"
,
1
)
)
)
return
DeviceStatus
(
friendly_name
,
model_name
,
manufacturer
,
api_version
)
except
(
requests
.
exceptions
.
RequestException
,
ET
.
ParseError
)
:
return


Returns the status of the specified app or else the current running app .

def get_app_status(host, app_id=None):
    """ Returns the status of the specified app
        or else the current running app. """
    # /apps/ will redirect to the active app
    url = (FORMAT_APP_PATH.format(host, app_id) if app_id
           else FORMAT_BASE_URL.format(host) + "/apps/")

    try:
        req = CC_SESSION.get(url)

        if req.status_code == 204:
            return None

        status_el = ET.fromstring(req.text.encode("UTF-8"))
        options = status_el.find(XML_NS_DIAL + "options").attrib

        app_id = _read_xml_element(status_el, XML_NS_DIAL,
                                   "name", "Unknown application")

        state = _read_xml_element(status_el, XML_NS_DIAL,
                                  "state", "unknown")

        service_el = status_el.find(XML_NS_CAST + "servicedata")

        if service_el is not None:
            service_url = _read_xml_element(service_el, XML_NS_CAST,
                                            "connectionSvcURL", None)

            protocols_el = service_el.find(XML_NS_CAST + "protocols")

            if protocols_el is not None:
                protocols = [el.text for el in protocols_el]
            else:
                protocols = []

        else:
            service_url = None
            protocols = []

        activity_el = status_el.find(XML_NS_CAST + "activity-status")

        if activity_el is not None:
            description = _read_xml_element(activity_el, XML_NS_CAST,
                                            "description", app_id)
        else:
            description = app_id

        return AppStatus(app_id, description, state,
                         options, service_url, protocols)

    except (requests.exceptions.RequestException, ET.ParseError):
        return None


Helper method to create a ChromeCast url given a host and an optional app_id .

def _craft_app_url(host, app_id=None):
    """ Helper method to create a ChromeCast url given
        a host and an optional app_id. """
    return (FORMAT_APP_PATH.format(host, app_id) if app_id
            else FORMAT_BASE_URL.format(host))

def _craft_app_url ( host , app_id = None ) : return ( FORMAT_APP_PATH . format ( host , app_id ) if app_id else FORMAT_BASE_URL . format ( host ) )


Helper method to read text from an element .

def _read_xml_element(element, xml_ns, tag_name, default=""):
    """ Helper method to read text from an element. """
    try:
        return element.find(xml_ns + tag_name).text

    except AttributeError:
        return default

def _read_xml_element ( element , xml_ns , tag_name , default = "" ) : try : return element . find ( xml_ns + tag_name ) . text except AttributeError : return default


Sends a message over the network to discover Chromecasts and returns a list of found IP addresses .

def discover_chromecasts(max_devices=None, timeout=DISCOVER_TIMEOUT):
    """
    Sends a message over the network to discover Chromecasts and returns
    a list of found IP addresses.

    Inspired by Crimsdings
    https://github.com/crimsdings/ChromeCast/blob/master/cc_discovery.py
    """
    ips = []

    calc_now = dt.datetime.now
    start = calc_now()

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

        sock.sendto(SSDP_REQUEST.encode("ascii"), (SSDP_ADDR, SSDP_PORT))

        sock.setblocking(0)

        while True:
            time_diff = calc_now() - start

            seconds_left = timeout - time_diff.seconds

            if seconds_left <= 0:
                return ips

            ready = select.select([sock], [], [], seconds_left)[0]

            if ready:
                response = sock.recv(1024).decode("ascii")

                found_ip = found_st = None

                headers = response.split("\r\n\r\n", 1)[0]

                for header in headers.split("\r\n"):
                    parts = header.split(": ", 1)

                    # Headers start with something like 'HTTP/1.1 200 OK'
                    # We cannot split that up in key-value pair, so skip
                    if len(parts) != 2:
                        continue

                    key, value = parts

                    if key == "LOCATION":
                        url = urlparse.urlparse(value)

                        found_ip = url.hostname

                    elif key == "ST":
                        found_st = value

                if found_st == SSDP_ST and found_ip:
                    ips.append(found_ip)

                    if max_devices and len(ips) == max_devices:
                        return ips

    except socket.error:
        logging.getLogger(__name__).exception(
            "Socket error while discovering Chromecasts")

    finally:
        sock.close()

    return ips


def get_possible_app_ids():
    """ Returns all possible app ids. """

    try:
        req = requests.get(
            "https://clients3.google.com/cast/chromecast/device/baseconfig")
        data = json.loads(req.text[4:])

        return [app['app_id'] for app in data['applications']] + \
            data["enabled_app_ids"]

    except ValueError:
        # If json fails to parse
        return []


def get_app_config(app_id):
    """ Get specific configuration for 'app_id'. """
    try:
        req = requests.get(
            ("https://clients3.google.com/"
             "cast/chromecast/device/app?a={}").format(app_id))

        return json.loads(req.text[4:]) if req.status_code == 200 else {}

    except ValueError:
        # If json fails to parse
        return {}

def
get_app_config
(
app_id
)
:
try
:
req
=
requests
.
get
(
(
"https://clients3.google.com/"
"cast/chromecast/device/app?a={}"
)
.
format
(
app_id
)
)
return
json
.
loads
(
req
.
text
[
4
:
]
)
if
req
.
status_code
==
200
else
{
}
except
ValueError
:
# If json fails to parse
return
{
}


Creates and returns a RAMP client based on the supplied app status . Will return None if RAMP client is not supported . Will raise ValueError if unable to retrieve the websocket url .

def create_websocket_client(app_status):
    """
    Creates and returns a RAMP client based on the supplied app status.
    Will return None if RAMP client is not supported.
    Will raise ValueError if unable to retrieve the websocket url.
    """

    # Check if current app has no service url or no protocols.
    if not app_status.service_url or not app_status.service_protocols:
        return None

    req = requests.post(app_status.service_url,
                        data="{}".encode("ascii"),
                        headers={"Content-Type": "application/json"})

    if req.status_code != 200:
        raise error.ConnectionError(
            "Could not retrieve websocket url ({}).".format(req.status_code))

    conn_data = json.loads(req.text)

    client = ChromecastWebSocketClient(conn_data['URL'],
                                       app_status.service_protocols)

    client.connect()

    atexit.register(_clean_open_clients)

    return client


Called on exit of Python to close open clients .

def _clean_open_clients():
    """ Called on exit of Python to close open clients. """
    for client_weakref in list(_OPEN_CLIENTS):
        client = client_weakref()

        if client and not client.terminated:
            client.close_connection()

def _clean_open_clients ( ) : for client_weakref in list ( _OPEN_CLIENTS ) : client = client_weakref ( ) if client and not client . terminated : client . close_connection ( )


When connection is opened initiate the protocol handlers .

def opened(self):
        """ When connection is opened initiate the protocol handlers. """
        _OPEN_CLIENTS.append(self._weakref)

        self.handlers[PROTOCOL_COMMAND] = CommandSubprotocol(self)

        _known_prot = KNOWN_PROTOCOLS

        # Instantiate supported subprotocols.
        for protocol in self.supported_protocols:
            handler = _known_prot.get(protocol)

            if handler:
                self.handlers[protocol] = handler(self)
            else:
                self.logger.warning(
                    "Unsupported protocol: {}".format(protocol))

def
opened
(
self
)
:
_OPEN_CLIENTS
.
append
(
self
.
_weakref
)
self
.
handlers
[
PROTOCOL_COMMAND
]
=
CommandSubprotocol
(
self
)
_known_prot
=
KNOWN_PROTOCOLS
# Instantiate supported subprotocols.
for
protocol
in
self
.
supported_protocols
:
handler
=
_known_prot
.
get
(
protocol
)
if
handler
:
self
.
handlers
[
protocol
]
=
handler
(
self
)
else
:
self
.
logger
.
warning
(
"Unsupported protocol: {}"
.
format
(
protocol
)
)


Clear protocol handlers when connection is lost .

def closed(self, code, reason=None):
        """ Clear protocol handlers when connection is lost. """
        # Clear reference to client
        _OPEN_CLIENTS.remove(self._weakref)

        for handler in self.handlers.values():
            handler.client = None

        self.handlers.clear()

def
closed
(
self
,
code
,
reason
=
)
:
# Clear reference to client
_OPEN_CLIENTS
.
remove
(
self
.
_weakref
)
for
handler
in
self
.
handlers
.
values
(
)
:
handler
.
client
=
self
.
handlers
.
clear
(
)


def received_message(self, message):
        """ When a new message is received. """

        # We do not support binary message
        if message.is_binary:
            return False

        try:
            protocol, data = json.loads(message.data.decode('utf8'))
        except ValueError:
            # If error while parsing JSON
            # if unpack error: more then 2 items in the list
            logging.getLogger(__name__).exception(
                "Error parsing incoming message: {}".format(
                    message.data.decode("utf8")))

            return

        if _DEBUG:
            logging.getLogger(__name__).info("Receiving {}".format(data))

        handler = self.handlers.get(protocol)

        if handler:
            handler._receive_protocol(data)  # pylint: disable=protected-access
        else:
            logging.getLogger(__name__).warning(
                "Unknown protocol received: {}, {}".format(protocol, data))


Default handler for sending messages as subprotocol .

def _send_protocol(self, data):
        """ Default handler for sending messages as subprotocol. """
        if _DEBUG:
            self.logger.info("Sending {}".format(data))

        if not self.client:
            raise error.ConnectionError("Not connected to Chromecast")

        try:
            self.client.send(json.dumps([self.protocol, data]).encode("utf8"))

        except socket.error:
            # if an error occured sending data over the socket
            raise error.ConnectionError("Error communicating with Chromecast")


Default handler for receiving messages as subprotocol .

def _receive_protocol(self, data):
        """ Default handler for receiving messages as subprotocol. """
        self.logger.warning(
            "Unhandled {} message: {}".format(self.protocol, data))

def
_receive_protocol
(
self
,
data
)
:
self
.
logger
.
warning
(
"Unhandled {} message: {}"
.
format
(
self
.
protocol
,
data
)
)


def is_active(self):
        """ Returns if this subprotocol is active. """
        return not self.client.terminated

def is_active ( self ) : return not self . client . terminated


def _receive_protocol(self, data):
        """ Handles an incoming COMMAND message. """

        if data[COMMAND_ATTR_TYPE] == COMMAND_TYPE_PING:
            self._send_protocol({COMMAND_ATTR_TYPE: COMMAND_TYPE_PONG})
        else:
            BaseSubprotocol._receive_protocol(self, data)

def _receive_protocol ( self , data ) : if data [ COMMAND_ATTR_TYPE ] == COMMAND_TYPE_PING : self . _send_protocol ( { COMMAND_ATTR_TYPE : COMMAND_TYPE_PONG } ) else : BaseSubprotocol . _receive_protocol ( self , data )


def _receive_protocol(self, data):
        """ Handles an incoming Ramp message. """
        message_type = data[RAMP_ATTR_TYPE]

        if message_type == RAMP_TYPE_STATUS:
            self._update_status(data[RAMP_ATTR_STATUS])

        elif message_type == RAMP_TYPE_RESPONSE:
            # Match it with the command that we send
            try:
                cmd_type, cmd_event = \
                    self.commands.pop(data[RAMP_ATTR_CMD_ID])

            except KeyError:
                # If CMD_ID did not exist or we do not recognize command
                return

            # Handle response, currently no response handlers
            if cmd_type in (RAMP_TYPE_PLAY, RAMP_TYPE_VOLUME,
                            RAMP_TYPE_INFO):

                self._update_status(data[RAMP_ATTR_STATUS])

            else:
                self.logger.warning(
                    "Unhandled response for command {}: {}".format(
                        cmd_type, data))

            # Alert code that is waiting for this command to get response
            if cmd_event:
                cmd_event.set()

        else:
            BaseSubprotocol._receive_protocol(self, data)


Sends a RAMP message . Set blocking = True to wait till the Chromecast sends a response to the command .

def _send_ramp(self, data, blocking=False):
        """
        Sends a RAMP message.
        Set blocking=True to wait till the Chromecast sends a response
        to the command.
        """
        data[RAMP_ATTR_CMD_ID] = self.command_id

        event = threading.Event() if blocking else None

        # Save type to match later with response
        self.commands[self.command_id] = (data[RAMP_ATTR_TYPE], event)

        self._send_protocol(data)

        self.command_id += 1

        if blocking:
            event.wait()


Property that represents if content is being played .

def is_playing(self):
        """ Property that represents if content is being played. """
        return self.is_active and self.state == RAMP_STATE_PLAYING

def is_playing ( self ) : return self . is_active and self . state == RAMP_STATE_PLAYING


Send the PLAY - command to the RAMP - target .

def play(self):
        """ Send the PLAY-command to the RAMP-target. """
        self._send_ramp({RAMP_ATTR_TYPE: RAMP_TYPE_PLAY})

def play ( self ) : self . _send_ramp ( { RAMP_ATTR_TYPE : RAMP_TYPE_PLAY } )


Send the PAUSE - command to the RAMP - target .

def pause(self):
        """ Send the PAUSE-command to the RAMP-target. """
        # The STOP command actually pauses the media
        self._send_ramp({RAMP_ATTR_TYPE: RAMP_TYPE_STOP})


def playpause(self):
        """ Plays if paused, pauses if playing. """
        if self.state == RAMP_STATE_PLAYING:
            self.pause()
        else:
            self.play()

def playpause ( self ) : if self . state == RAMP_STATE_PLAYING : self . pause ( ) else : self . play ( )


Seek within the content played at RAMP - target .

def seek(self, seconds):
        """ Seek within the content played at RAMP-target. """
        self._send_ramp({RAMP_ATTR_TYPE: RAMP_TYPE_PLAY,
                         RAMP_ATTR_POSITION: seconds})

def seek ( self , seconds ) : self . _send_ramp ( { RAMP_ATTR_TYPE : RAMP_TYPE_PLAY , RAMP_ATTR_POSITION : seconds } )


def rewind(self):
        """ Rewinds current media item. """
        self.seek(0)


Skip to the next content at the RAMP - target .

def next(self):
        """ Skip to the next content at the RAMP-target. """
        if self.duration != 0:
            self.seek(self.duration-.1)

def next ( self ) : if self . duration != 0 : self . seek ( self . duration - .1 )


def set_volume(self, volume):
        """ Set volume at the RAMP-target. """
        # volume is double between 0 and 1
        self._send_ramp({RAMP_ATTR_TYPE: RAMP_TYPE_VOLUME,
                         RAMP_ATTR_VOLUME: volume})

def
set_volume
(
self
,
volume
)
:
# volume is double between 0 and 1
self
.
_send_ramp
(
{
RAMP_ATTR_TYPE
:
RAMP_TYPE_VOLUME
,
RAMP_ATTR_VOLUME
:
volume
}
)


def volume_up(self):
        """ Increases volume. """
        if self.volume < 1:
            self.set_volume(min(self.volume+.1, 1))

def volume_up ( self ) : if self . volume < 1 : self . set_volume ( min ( self . volume + .1 , 1 ) )


def volume_down(self):
        """ Decreases volume. """
        if self.volume > 0:
            self.set_volume(max(self.volume-.1, 0))

def volume_down ( self ) : if self . volume > 0 : self . set_volume ( max ( self . volume - .1 , 0 ) )


def refresh(self):
        """ Refresh data at the RAMP-target. """
        self._send_ramp({RAMP_ATTR_TYPE: RAMP_TYPE_INFO})

def refresh ( self ) : self . _send_ramp ( { RAMP_ATTR_TYPE : RAMP_TYPE_INFO } )


def _update_status(self, status):
        """ Updates the RAMP status. """
        con_inf = status.get(RAMP_ATTR_CONTENT_INFO, {})

        self.state = status.get(RAMP_STATUS_ATTR_STATE, 0)
        self.volume = status.get(RAMP_STATUS_ATTR_VOLUME, 1)
        self.muted = status.get(RAMP_STATUS_ATTR_MUTED, False)
        self.content_id = status.get(RAMP_STATUS_ATTR_CONTENT_ID)
        self.title = status.get(RAMP_STATUS_ATTR_TITLE)
        self.artist = con_inf.get(RAMP_STATUS_CONTENT_INFO_ATTR_ARTIST)
        self.album = con_inf.get(RAMP_STATUS_CONTENT_INFO_ATTR_ALBUM_TITLE)
        self._current_time = status.get(RAMP_STATUS_ATTR_CURRENT_TIME, 0)
        self.duration = status.get(RAMP_STATUS_ATTR_DURATION, 0)
        self.image_url = status.get(RAMP_STATUS_ATTR_IMAGE_URL)
        self.time_progress = status.get(RAMP_ATTR_TIME_PROGRESS, False)

        self.last_updated = dt.datetime.now()


def current_time(self):
        """ Returns current time of the content. """

        # If time is progressing we have to calculate the current time based on
        # time the status was retrieved and the then current time.
        if self.time_progress:
            timediff = dt.datetime.now() - self.last_updated

            return min(self._current_time + timediff.seconds, self.duration)

        else:
            return self._current_time


Starts the YouTube app if it is not running and plays specified video .

def play_youtube_video(video_id, host=None):
    """ Starts the YouTube app if it is not running and plays
        specified video. """

    if not host:
        host = _auto_select_chromecast()

    start_app(host, APP_ID["YOUTUBE"], {"v": video_id})

def play_youtube_video ( video_id , host = None ) : if not host : host = _auto_select_chromecast ( ) start_app ( host , APP_ID [ "YOUTUBE" ] , { "v" : video_id } )


Starts the YouTube app if it is not running and plays specified playlist .

def play_youtube_playlist(playlist_id, host=None):
    """ Starts the YouTube app if it is not running and plays
        specified playlist. """

    if not host:
        host = _auto_select_chromecast()

    start_app(host, APP_ID["YOUTUBE"],
              {"listType": "playlist", "list": playlist_id})

def play_youtube_playlist ( playlist_id , host = None ) : if not host : host = _auto_select_chromecast ( ) start_app ( host , APP_ID [ "YOUTUBE" ] , { "listType" : "playlist" , "list" : playlist_id } )


Discovers local Chromecasts and returns first one found . Raises exception if none can be found .

def _auto_select_chromecast():
    """
    Discovers local Chromecasts and returns first one found.
    Raises exception if none can be found.
    """
    ips = discover_chromecasts(1)

    if ips:
        return ips[0]
    else:
        raise NoChromecastFoundError("Unable to detect Chromecast")

def
_auto_select_chromecast
(
)
:
ips
=
discover_chromecasts
(
1
)
if
ips
:
return
ips
[
0
]
else
:
raise
NoChromecastFoundError
(
"Unable to detect Chromecast"
)


def app_id(self):
        """ Returns the current app_id. """
        return self.app.app_id if self.app else None

def app_id ( self ) : return self . app . app_id if self . app else None


Returns the name of the current running app .

def app_description(self):
        """ Returns the name of the current running app. """
        return self.app.description if self.app else None

def app_description ( self ) : return self . app . description if self . app else None


Returns the current RAMP content info and controls .

def get_protocol(self, protocol):
        """ Returns the current RAMP content info and controls. """
        if self.websocket_client:
            return self.websocket_client.handlers.get(protocol)
        else:
            return None

def get_protocol ( self , protocol ) : if self . websocket_client : return self . websocket_client . handlers . get ( protocol ) else : return None


Queries the Chromecast for the current status . Starts a websocket client if possible .

def refresh(self):
        """
        Queries the Chromecast for the current status.
        Starts a websocket client if possible.
        """
        self.logger.info("Refreshing app status")

        # If we are refreshing but a refresh was planned, cancel that one
        with self._refresh_lock:
            if self._refresh_timer:
                self._refresh_timer.cancel()
                self._refresh_timer = None

        cur_app = self.app
        cur_ws = self.websocket_client

        self.app = app = get_app_status(self.host)

        # If no previous app and no new app there is nothing to do
        if not cur_app and not app:
            is_diff_app = False
        else:
            is_diff_app = (not cur_app and app or cur_app and not app or
                           cur_app.app_id != app.app_id)

        # Clean up websocket if:
        #  - there is a different app and a connection exists
        #  - if it is the same app but the connection is terminated
        if cur_ws and (is_diff_app or cur_ws.terminated):

            if not cur_ws.terminated:
                cur_ws.close_connection()

            self.websocket_client = cur_ws = None

        # Create a new websocket client if there is no connection
        if not cur_ws and app:

            try:
                # If the current app is not capable of a websocket client
                # This method will return None so nothing is lost
                self.websocket_client = cur_ws = create_websocket_client(app)

            except ConnectionError:
                pass

            # Ramp service does not always immediately show up in the app
            # status. If we do not have a websocket client but the app is
            # known to be RAMP controllable, then plan refresh.
            if not cur_ws and app.app_id in RAMP_ENABLED:
                self._delayed_refresh()


def start_app(self, app_id, data=None):
        """ Start an app on the Chromecast. """
        self.logger.info("Starting app {}".format(app_id))

        # data parameter has to contain atleast 1 key
        # or else some apps won't show
        start_app(self.host, app_id, data)

        self._delayed_refresh()


Tells the Chromecast to quit current app_id .

def quit_app(self):
        """ Tells the Chromecast to quit current app_id. """
        self.logger.info("Quiting current app")

        quit_app(self.host)

        self._delayed_refresh()

def
quit_app
(
self
)
:
self
.
logger
.
info
(
"Quiting current app"
)
quit_app
(
self
.
host
)
self
.
_delayed_refresh
(
)


Give the ChromeCast time to start the app then refresh app .

def _delayed_refresh(self):
        """ Give the ChromeCast time to start the app, then refresh app. """
        with self._refresh_lock:
            if self._refresh_timer:
                self._refresh_timer.cancel()

            self._refresh_timer = threading.Timer(5, self.refresh)
            self._refresh_timer.start()

def _delayed_refresh ( self ) : with self . _refresh_lock : if self . _refresh_timer : self . _refresh_timer . cancel ( ) self . _refresh_timer = threading . Timer ( 5 , self . refresh ) self . _refresh_timer . start ( )