From e2d9e233dab2d532877acb2e3b5b73943993cd98 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Thu, 27 Mar 2025 20:49:24 -0500 Subject: [PATCH] Added ability to send a next stream command. --- apps/proxy/ts_proxy/stream_manager.py | 3 +- apps/proxy/ts_proxy/url_utils.py | 14 ++--- apps/proxy/ts_proxy/urls.py | 1 + apps/proxy/ts_proxy/views.py | 76 ++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 10 deletions(-) diff --git a/apps/proxy/ts_proxy/stream_manager.py b/apps/proxy/ts_proxy/stream_manager.py index 6cf31ffa..317ec176 100644 --- a/apps/proxy/ts_proxy/stream_manager.py +++ b/apps/proxy/ts_proxy/stream_manager.py @@ -888,7 +888,8 @@ class StreamManager: self.buffer.redis_client.hset(metadata_key, mapping={ ChannelMetadataField.URL: new_url, ChannelMetadataField.USER_AGENT: new_user_agent, - ChannelMetadataField.STREAM_PROFILE: stream_info['profile'], + ChannelMetadataField.STREAM_PROFILE: stream_info['stream_profile'], + ChannelMetadataField.M3U_PROFILE: stream_info['m3u_profile_id'], ChannelMetadataField.STREAM_ID: str(stream_id), ChannelMetadataField.STREAM_SWITCH_TIME: str(time.time()), ChannelMetadataField.STREAM_SWITCH_REASON: "max_retries_exceeded" diff --git a/apps/proxy/ts_proxy/url_utils.py b/apps/proxy/ts_proxy/url_utils.py index f68caead..c4767dd1 100644 --- a/apps/proxy/ts_proxy/url_utils.py +++ b/apps/proxy/ts_proxy/url_utils.py @@ -132,21 +132,21 @@ def get_stream_info_for_switch(channel_id: str, target_stream_id: Optional[int] ).first() if default_profile: - profile_id = default_profile.id + m3u_profile_id = default_profile.id else: logger.error(f"No profile found for stream {stream_id}") return {'error': 'No profile found for stream'} else: # Use first available profile - profile_id = profiles.first().id + m3u_profile_id = profiles.first().id else: - stream_id, profile_id = channel.get_stream() - if stream_id is None or profile_id is None: + stream_id, m3u_profile_id = channel.get_stream() + if stream_id is None or m3u_profile_id is None: return {'error': 'No stream assigned to channel'} # Get the stream and profile objects directly stream = get_object_or_404(Stream, pk=stream_id) - profile = get_object_or_404(M3UAccountProfile, pk=profile_id) + profile = get_object_or_404(M3UAccountProfile, pk=m3u_profile_id) # Get the user agent from the M3U account m3u_account = M3UAccount.objects.get(id=profile.m3u_account.id) @@ -166,9 +166,9 @@ def get_stream_info_for_switch(channel_id: str, target_stream_id: Optional[int] 'url': stream_url, 'user_agent': user_agent, 'transcode': transcode, - 'profile': profile_value, + 'stream_profile': profile_value, 'stream_id': stream_id, - 'profile_id': profile_id + 'm3u_profile_id': m3u_profile_id } except Exception as e: logger.error(f"Error getting stream info for switch: {e}", exc_info=True) diff --git a/apps/proxy/ts_proxy/urls.py b/apps/proxy/ts_proxy/urls.py index cba06b6f..cb236aa2 100644 --- a/apps/proxy/ts_proxy/urls.py +++ b/apps/proxy/ts_proxy/urls.py @@ -10,4 +10,5 @@ urlpatterns = [ path('status/', views.channel_status, name='channel_status_detail'), path('stop/', views.stop_channel, name='stop_channel'), path('stop_client/', views.stop_client, name='stop_client'), + path('next_stream/', views.next_stream, name='next_stream'), ] diff --git a/apps/proxy/ts_proxy/views.py b/apps/proxy/ts_proxy/views.py index 8455d490..67a75c4d 100644 --- a/apps/proxy/ts_proxy/views.py +++ b/apps/proxy/ts_proxy/views.py @@ -21,7 +21,7 @@ from rest_framework.permissions import IsAuthenticated from .constants import ChannelState, EventType, StreamType, ChannelMetadataField from .config_helper import ConfigHelper from .services.channel_service import ChannelService -from .url_utils import generate_stream_url, transform_url, get_stream_info_for_switch, get_stream_object +from .url_utils import generate_stream_url, transform_url, get_stream_info_for_switch, get_stream_object, get_alternate_streams from .utils import get_logger from uuid import UUID @@ -189,7 +189,7 @@ def stream_ts(request, channel_id): @csrf_exempt @api_view(['POST']) -@permission_classes([IsAuthenticated]) +#@permission_classes([IsAuthenticated]) def change_stream(request, channel_id): """Change stream URL for existing channel with enhanced diagnostics""" try: @@ -337,3 +337,75 @@ def stop_client(request, channel_id): except Exception as e: logger.error(f"Failed to stop client: {e}", exc_info=True) return JsonResponse({'error': str(e)}, status=500) + +@csrf_exempt +@api_view(['POST']) +#@permission_classes([IsAuthenticated]) +def next_stream(request, channel_id): + """Switch to the next available stream for a channel""" + 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) + + # Get current stream info to know which one we're currently using + current_stream_id, profile_id = channel.get_stream() + + if not current_stream_id: + return JsonResponse({'error': 'No current stream found for channel'}, status=404) + + # Get alternate streams excluding the current one + alternate_streams = get_alternate_streams(channel_id, current_stream_id) + + if not alternate_streams: + return JsonResponse({ + 'error': 'No alternate streams available for this channel', + 'current_stream_id': current_stream_id + }, status=404) + + # Pick the next stream from alternatives + next_stream = alternate_streams[0] # Get the first alternative + next_stream_id = next_stream['stream_id'] + + # Get full stream info including URL for the next stream + stream_info = get_stream_info_for_switch(channel_id, next_stream_id) + + if 'error' in stream_info: + return JsonResponse({ + 'error': stream_info['error'], + 'current_stream_id': current_stream_id, + 'next_stream_id': next_stream_id + }, status=404) + + # Now use the ChannelService to change the stream URL + result = ChannelService.change_stream_url( + channel_id, + stream_info['url'], + stream_info['user_agent'] + ) + + if result.get('status') == 'error': + return JsonResponse({ + 'error': result.get('message', 'Unknown error'), + 'diagnostics': result.get('diagnostics', {}), + 'current_stream_id': current_stream_id, + 'next_stream_id': next_stream_id + }, status=404) + + # Format success response + response_data = { + 'message': 'Stream switched to next available', + 'channel': channel_id, + 'previous_stream_id': current_stream_id, + 'new_stream_id': next_stream_id, + 'new_url': stream_info['url'], + 'owner': result.get('direct_update', False), + 'worker_id': proxy_server.worker_id + } + + return JsonResponse(response_data) + + except Exception as e: + logger.error(f"Failed to switch to next stream: {e}", exc_info=True) + return JsonResponse({'error': str(e)}, status=500)