diff --git a/apps/proxy/ts_proxy/services/channel_service.py b/apps/proxy/ts_proxy/services/channel_service.py index 8b8a3d26..72209c96 100644 --- a/apps/proxy/ts_proxy/services/channel_service.py +++ b/apps/proxy/ts_proxy/services/channel_service.py @@ -95,6 +95,7 @@ class ChannelService: dict: Result information including success status and diagnostics """ # If no direct URL is provided but a target stream is, get URL from target stream + stream_id = None if not new_url and target_stream_id: stream_info = get_stream_info_for_switch(channel_id, target_stream_id) if 'error' in stream_info: @@ -104,6 +105,10 @@ class ChannelService: } new_url = stream_info['url'] user_agent = stream_info['user_agent'] + stream_id = target_stream_id + elif target_stream_id: + # If we have both URL and target_stream_id, use the target_stream_id + stream_id = target_stream_id # Check if channel exists in_local_managers = channel_id in proxy_server.stream_managers @@ -155,7 +160,7 @@ class ChannelService: # Update metadata in Redis regardless of ownership if proxy_server.redis_client: try: - ChannelService._update_channel_metadata(channel_id, new_url, user_agent) + ChannelService._update_channel_metadata(channel_id, new_url, user_agent, stream_id) result['metadata_updated'] = True except Exception as e: logger.error(f"Error updating Redis metadata: {e}", exc_info=True) @@ -180,7 +185,7 @@ class ChannelService: # If we're not the owner, publish an event for the owner to pick up logger.info(f"Not the owner, requesting URL change via Redis PubSub") if proxy_server.redis_client: - ChannelService._publish_stream_switch_event(channel_id, new_url, user_agent) + ChannelService._publish_stream_switch_event(channel_id, new_url, user_agent, stream_id) result.update({ 'direct_update': False, 'event_published': True, @@ -400,7 +405,7 @@ class ChannelService: # Helper methods for Redis operations @staticmethod - def _update_channel_metadata(channel_id, url, user_agent=None): + def _update_channel_metadata(channel_id, url, user_agent=None, stream_id=None): """Update channel metadata in Redis""" if not proxy_server.redis_client: return False @@ -411,23 +416,22 @@ class ChannelService: key_type = proxy_server.redis_client.type(metadata_key).decode('utf-8') logger.debug(f"Redis key {metadata_key} is of type: {key_type}") + # Build metadata update dict + metadata = {ChannelMetadataField.URL: url} + if user_agent: + metadata[ChannelMetadataField.USER_AGENT] = user_agent + if stream_id: + metadata[ChannelMetadataField.STREAM_ID] = str(stream_id) + logger.info(f"Updating stream ID to {stream_id} in Redis for channel {channel_id}") + # Use the appropriate method based on the key type if key_type == 'hash': - proxy_server.redis_client.hset(metadata_key, ChannelMetadataField.URL, url) - if user_agent: - proxy_server.redis_client.hset(metadata_key, ChannelMetadataField.USER_AGENT, user_agent) + proxy_server.redis_client.hset(metadata_key, mapping=metadata) elif key_type == 'none': # Key doesn't exist yet - # Create new hash with all required fields - metadata = {ChannelMetadataField.URL: url} - if user_agent: - metadata[ChannelMetadataField.USER_AGENT] = user_agent proxy_server.redis_client.hset(metadata_key, mapping=metadata) else: # If key exists with wrong type, delete it and recreate proxy_server.redis_client.delete(metadata_key) - metadata = {ChannelMetadataField.URL: url} - if user_agent: - metadata[ChannelMetadataField.USER_AGENT] = user_agent proxy_server.redis_client.hset(metadata_key, mapping=metadata) # Set switch request flag to ensure all workers see it @@ -438,7 +442,7 @@ class ChannelService: return True @staticmethod - def _publish_stream_switch_event(channel_id, new_url, user_agent=None): + def _publish_stream_switch_event(channel_id, new_url, user_agent=None, stream_id=None): """Publish a stream switch event to Redis pubsub""" if not proxy_server.redis_client: return False @@ -448,6 +452,7 @@ class ChannelService: "channel_id": channel_id, "url": new_url, "user_agent": user_agent, + "stream_id": stream_id, "requester": proxy_server.worker_id, "timestamp": time.time() } diff --git a/apps/proxy/ts_proxy/url_utils.py b/apps/proxy/ts_proxy/url_utils.py index c4767dd1..04440c03 100644 --- a/apps/proxy/ts_proxy/url_utils.py +++ b/apps/proxy/ts_proxy/url_utils.py @@ -194,8 +194,8 @@ def get_alternate_streams(channel_id: str, current_stream_id: Optional[int] = No logger.debug(f"Looking for alternate streams for channel {channel_id}, current stream ID: {current_stream_id}") - # Get all assigned streams for this channel - streams = channel.streams.all() + # Get all assigned streams for this channel using the correct ordering from the channelstream table + streams = channel.streams.all().order_by('channelstream__order') logger.debug(f"Channel {channel_id} has {streams.count()} total assigned streams") if not streams.exists(): @@ -204,7 +204,7 @@ def get_alternate_streams(channel_id: str, current_stream_id: Optional[int] = No alternate_streams = [] - # Process each stream + # Process each stream in the user-defined order for stream in streams: # Log each stream we're checking logger.debug(f"Checking stream ID {stream.id} ({stream.name}) for channel {channel_id}") @@ -215,8 +215,6 @@ def get_alternate_streams(channel_id: str, current_stream_id: Optional[int] = No continue # Find compatible profiles for this stream - # FIX: Looking at the error message, M3UAccountProfile doesn't have a 'stream' field - # We need to find which field relates M3UAccountProfile to Stream try: # Check if we can find profiles via m3u_account profiles = M3UAccountProfile.objects.filter(m3u_account=stream.m3u_account) diff --git a/apps/proxy/ts_proxy/views.py b/apps/proxy/ts_proxy/views.py index 67a75c4d..375a7b79 100644 --- a/apps/proxy/ts_proxy/views.py +++ b/apps/proxy/ts_proxy/views.py @@ -346,13 +346,27 @@ def next_stream(request, channel_id): try: logger.info(f"Request to switch to next stream for channel {channel_id} received") - # Check if the channel exists - channel = get_stream_object(channel_id) + # First check if channel is active in Redis + current_stream_id = None + profile_id = None - # Get current stream info to know which one we're currently using - current_stream_id, profile_id = channel.get_stream() + if proxy_server.redis_client: + metadata_key = RedisKeys.channel_metadata(channel_id) + if proxy_server.redis_client.exists(metadata_key): + # Get current stream ID from Redis + stream_id_bytes = proxy_server.redis_client.hget(metadata_key, ChannelMetadataField.STREAM_ID) + if stream_id_bytes: + current_stream_id = int(stream_id_bytes.decode('utf-8')) + logger.info(f"Found current stream ID {current_stream_id} in Redis for channel {channel_id}") + + # Get M3U profile from Redis if available + profile_id_bytes = proxy_server.redis_client.hget(metadata_key, ChannelMetadataField.M3U_PROFILE) + if profile_id_bytes: + profile_id = int(profile_id_bytes.decode('utf-8')) + logger.info(f"Found M3U profile ID {profile_id} in Redis for channel {channel_id}") if not current_stream_id: + # Channel is not running return JsonResponse({'error': 'No current stream found for channel'}, status=404) # Get alternate streams excluding the current one @@ -382,7 +396,8 @@ def next_stream(request, channel_id): result = ChannelService.change_stream_url( channel_id, stream_info['url'], - stream_info['user_agent'] + stream_info['user_agent'], + next_stream_id # Pass the stream_id to be stored in Redis ) if result.get('status') == 'error':