# # Aura Engine (https://gitlab.servus.at/aura/engine) # # Copyright (C) 2017-2020 - The Aura Engine Team. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from enum import Enum from src.base.utils import SimpleUtil as SU from src.core.resources import ResourceType class TransitionType(Enum): """ Types of fade-in and fade-out transition. """ INSTANT = "instant" FADE = "fade" class Channel(Enum): """ Channel name mappings to the Liqidsoap channel/source IDs. """ QUEUE_A = "in_filesystem_0" QUEUE_B = "in_filesystem_1" HTTP_A = "in_http_0" HTTP_B = "in_http_1" HTTPS_A = "in_https_0" HTTPS_B = "in_https_1" LIVE_0 = "linein_0" LIVE_1 = "linein_1" LIVE_2 = "linein_2" LIVE_3 = "linein_3" LIVE_4 = "linein_4" FALLBACK_QUEUE_A = "in_fallback_scheduled_0" FALLBACK_QUEUE_B = "in_fallback_scheduled_1" FALLBACK_STATION_FOLDER = "station_folder" FALLBACK_STATION_PLAYLIST = "station_playlist" def __str__(self): return str(self.value) class ChannelType(Enum): """ Engine channel types mapped to `Entry` source types. """ QUEUE = { "id": "fs", "numeric": 0, "channels": [Channel.QUEUE_A, Channel.QUEUE_B] } HTTP = { "id": "http", "numeric": 1, "channels": [Channel.HTTP_A, Channel.HTTP_B] } HTTPS = { "id": "https", "numeric": 2, "channels": [Channel.HTTPS_A, Channel.HTTPS_B] } LIVE = { "id": "live", "numeric": 3, "channels": [ Channel.LIVE_0, Channel.LIVE_1, Channel.LIVE_2, Channel.LIVE_3, Channel.LIVE_4 ] } FALLBACK_QUEUE = { "id": "fallback_queue", "numeric": 4, "channels": [Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B] } FALLBACK_POOL = { "id": "fallback_pool", "numeric": 5, "channels": [Channel.FALLBACK_STATION_FOLDER, Channel.FALLBACK_STATION_PLAYLIST] } @property def channels(self): return self.value["channels"] @property def numeric(self): return self.value["numeric"] def __str__(self): return str(self.value["id"]) class EntryPlayState(Enum): UNKNOWN = "unknown" LOADING = "loading" READY = "ready_to_play" PLAYING = "playing" FINISHED = "finished" class LiquidsoapResponse(Enum): SUCCESS = "Done" STREAM_STATUS_POLLING = "polling" STREAM_STATUS_STOPPED = "stopped" STREAM_STATUS_CONNECTED = "connected" class ChannelRouter(): """ Wires source types with channels and channel-types. """ config = None logger = None resource_mapping = None active_channels = None def __init__(self, config, logger): """ Constructor Args: config (AuraConfig): The configuration logger (Logger): The logger """ self.config = config self.logger = logger self.resource_mapping = { ResourceType.FILE: ChannelType.QUEUE, ResourceType.STREAM_HTTP: ChannelType.HTTP, ResourceType.STREAM_HTTPS: ChannelType.HTTPS, ResourceType.LINE: ChannelType.LIVE, ResourceType.PLAYLIST: ChannelType.QUEUE, ResourceType.POOL: ChannelType.QUEUE } self.active_channels = { ChannelType.QUEUE: Channel.QUEUE_A, ChannelType.FALLBACK_QUEUE: Channel.FALLBACK_QUEUE_A, ChannelType.HTTP: Channel.HTTP_A, ChannelType.HTTPS: Channel.HTTPS_A, ChannelType.LIVE: Channel.LIVE_0 } def set_active(self, channel_type, channel): """ Set the channel for the given resource type active """ self.active_channels[channel_type] = channel def get_active(self, channel_type): """ Retrieves the active channel for the given resource type """ return self.active_channels[channel_type] def type_of_channel(self, channel): """ Retrieves a `ChannelType` for the given `Channel`. """ if channel in ChannelType.QUEUE.channels: return ChannelType.QUEUE elif channel in ChannelType.FALLBACK_QUEUE.channels: return ChannelType.FALLBACK_QUEUE elif channel in ChannelType.HTTP.channels: return ChannelType.HTTP elif channel in ChannelType.HTTPS.channels: return ChannelType.HTTPS elif channel in ChannelType.LIVE.channels: return ChannelType.LIVE else: return None def type_for_resource(self, resource_type): """ Retrieves a `ChannelType` for the given `ResourceType`. Only default mappings can be evaluatated. Custom variations like fallback channels are not respected. """ return self.resource_mapping.get(resource_type) def channel_swap(self, channel_type): """ Returns the currently inactive channel for a given type. For example if the currently some file on channel QUEUE A is playing, the channel QUEUE B is returned for being used to queue new entries. Args: entry_type (ResourceType): The resource type such es file, stream or live source Returns: (Channel, Channel): The previous and new channel """ previous_channel = self.active_channels[channel_type] new_channel = None msg = None if channel_type == ChannelType.QUEUE: if previous_channel == Channel.QUEUE_A: new_channel = Channel.QUEUE_B msg = "Swapped queue channel from A > B" else: new_channel = Channel.QUEUE_A msg = "Swapped queue channel from B > A" elif channel_type == ChannelType.FALLBACK_QUEUE: if previous_channel == Channel.FALLBACK_QUEUE_A: new_channel = Channel.FALLBACK_QUEUE_B msg = "Swapped fallback queue channel from A > B" else: new_channel = Channel.FALLBACK_QUEUE_A msg = "Swapped fallback channel from B > A" elif channel_type == ChannelType.HTTP: if previous_channel == Channel.HTTP_A: new_channel = Channel.HTTP_B msg = "Swapped HTTP Stream channel from A > B" else: new_channel = Channel.HTTP_A msg = "Swapped HTTP Stream channel from B > A" elif channel_type == ChannelType.HTTPS: if previous_channel == Channel.HTTPS_A: new_channel = Channel.HTTPS_B msg = "Swapped HTTPS Stream channel from A > B" else: new_channel = Channel.HTTPS_A msg = "Swapped HTTPS Stream channel from B > A" else: self.logger.warning(SU.red(f"No channel to swap - invalid entry_type '{channel_type}'")) if msg: self.logger.info(SU.pink(msg)) return (previous_channel, new_channel)