Added ability to send a next stream command.

This commit is contained in:
SergeantPanda 2025-03-27 20:49:24 -05:00
parent ce6e019e6a
commit e2d9e233da
4 changed files with 84 additions and 10 deletions

View file

@ -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"

View file

@ -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)

View file

@ -10,4 +10,5 @@ urlpatterns = [
path('status/<str:channel_id>', views.channel_status, name='channel_status_detail'),
path('stop/<str:channel_id>', views.stop_channel, name='stop_channel'),
path('stop_client/<str:channel_id>', views.stop_client, name='stop_client'),
path('next_stream/<str:channel_id>', views.next_stream, name='next_stream'),
]

View file

@ -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)