diff --git a/apps/proxy/ts_proxy/channel_status.py b/apps/proxy/ts_proxy/channel_status.py
index fe5b1f3b..43e01df8 100644
--- a/apps/proxy/ts_proxy/channel_status.py
+++ b/apps/proxy/ts_proxy/channel_status.py
@@ -65,6 +65,25 @@ class ChannelStatus:
except ValueError:
logger.warning(f"Invalid stream_id format in Redis: {stream_id_bytes}")
+ # Add M3U profile information
+ m3u_profile_id_bytes = metadata.get(ChannelMetadataField.M3U_PROFILE.encode('utf-8'))
+ if m3u_profile_id_bytes:
+ try:
+ m3u_profile_id = int(m3u_profile_id_bytes.decode('utf-8'))
+ info['m3u_profile_id'] = m3u_profile_id
+
+ # Look up M3U profile name from database
+ try:
+ from apps.m3u.models import M3UAccountProfile
+ m3u_profile = M3UAccountProfile.objects.filter(id=m3u_profile_id).first()
+ if m3u_profile:
+ info['m3u_profile_name'] = m3u_profile.name
+ logger.debug(f"Added M3U profile name '{m3u_profile.name}' for profile ID {m3u_profile_id}")
+ except (ImportError, DatabaseError) as e:
+ logger.warning(f"Failed to get M3U profile name for ID {m3u_profile_id}: {e}")
+ except ValueError:
+ logger.warning(f"Invalid m3u_profile_id format in Redis: {m3u_profile_id_bytes}")
+
# Add timing information
state_changed_field = ChannelMetadataField.STATE_CHANGED_AT.encode('utf-8')
if state_changed_field in metadata:
@@ -380,6 +399,25 @@ class ChannelStatus:
# Add clients to info
info['clients'] = clients
+ # Add M3U profile information
+ m3u_profile_id_bytes = metadata.get(ChannelMetadataField.M3U_PROFILE.encode('utf-8'))
+ if m3u_profile_id_bytes:
+ try:
+ m3u_profile_id = int(m3u_profile_id_bytes.decode('utf-8'))
+ info['m3u_profile_id'] = m3u_profile_id
+
+ # Look up M3U profile name from database
+ try:
+ from apps.m3u.models import M3UAccountProfile
+ m3u_profile = M3UAccountProfile.objects.filter(id=m3u_profile_id).first()
+ if m3u_profile:
+ info['m3u_profile_name'] = m3u_profile.name
+ logger.debug(f"Added M3U profile name '{m3u_profile.name}' for profile ID {m3u_profile_id}")
+ except (ImportError, DatabaseError) as e:
+ logger.warning(f"Failed to get M3U profile name for ID {m3u_profile_id}: {e}")
+ except ValueError:
+ logger.warning(f"Invalid m3u_profile_id format in Redis: {m3u_profile_id_bytes}")
+
return info
except Exception as e:
logger.error(f"Error getting channel info: {e}")
diff --git a/apps/proxy/ts_proxy/services/channel_service.py b/apps/proxy/ts_proxy/services/channel_service.py
index 3ac62af4..9aa8c66b 100644
--- a/apps/proxy/ts_proxy/services/channel_service.py
+++ b/apps/proxy/ts_proxy/services/channel_service.py
@@ -7,7 +7,7 @@ import logging
import time
import json
from django.shortcuts import get_object_or_404
-from apps.channels.models import Channel
+from apps.channels.models import Channel, Stream
from apps.proxy.config import TSConfig as Config
from ..server import ProxyServer
from ..redis_keys import RedisKeys
@@ -58,7 +58,7 @@ class ChannelService:
# Verify the stream_id was set
stream_id_value = proxy_server.redis_client.hget(metadata_key, ChannelMetadataField.STREAM_ID)
if stream_id_value:
- logger.info(f"Verified stream_id {stream_id_value.decode('utf-8')} is now set in Redis")
+ logger.debug(f"Verified stream_id {stream_id_value.decode('utf-8')} is now set in Redis")
else:
logger.error(f"Failed to set stream_id {stream_id} in Redis before initialization")
@@ -82,7 +82,7 @@ class ChannelService:
return success
@staticmethod
- def change_stream_url(channel_id, new_url=None, user_agent=None, target_stream_id=None):
+ def change_stream_url(channel_id, new_url=None, user_agent=None, target_stream_id=None, m3u_profile_id=None):
"""
Change the URL of an existing stream.
@@ -91,6 +91,7 @@ class ChannelService:
new_url: New stream URL (optional if target_stream_id is provided)
user_agent: Optional user agent to update
target_stream_id: Optional target stream ID to switch to
+ m3u_profile_id: Optional M3U profile ID to update
Returns:
dict: Result information including success status and diagnostics
@@ -109,6 +110,10 @@ class ChannelService:
new_url = stream_info['url']
user_agent = stream_info['user_agent']
stream_id = target_stream_id
+ # Extract M3U profile ID from stream info if available
+ if 'm3u_profile_id' in stream_info:
+ m3u_profile_id = stream_info['m3u_profile_id']
+ logger.info(f"Found M3U profile ID {m3u_profile_id} for stream ID {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
@@ -163,7 +168,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, stream_id)
+ ChannelService._update_channel_metadata(channel_id, new_url, user_agent, stream_id, m3u_profile_id)
result['metadata_updated'] = True
except Exception as e:
logger.error(f"Error updating Redis metadata: {e}", exc_info=True)
@@ -188,7 +193,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, stream_id)
+ ChannelService._publish_stream_switch_event(channel_id, new_url, user_agent, stream_id, m3u_profile_id)
result.update({
'direct_update': False,
'event_published': True,
@@ -413,7 +418,7 @@ class ChannelService:
# Helper methods for Redis operations
@staticmethod
- def _update_channel_metadata(channel_id, url, user_agent=None, stream_id=None):
+ def _update_channel_metadata(channel_id, url, user_agent=None, stream_id=None, m3u_profile_id=None):
"""Update channel metadata in Redis"""
proxy_server = ProxyServer.get_instance()
@@ -432,7 +437,8 @@ class ChannelService:
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}")
+ if m3u_profile_id:
+ metadata[ChannelMetadataField.M3U_PROFILE] = str(m3u_profile_id)
# Use the appropriate method based on the key type
if key_type == 'hash':
@@ -448,11 +454,11 @@ class ChannelService:
switch_key = RedisKeys.switch_request(channel_id)
proxy_server.redis_client.setex(switch_key, 30, url) # 30 second TTL
- logger.info(f"Updated metadata for channel {channel_id} in Redis")
+ logger.debug(f"Updated metadata for channel {channel_id} in Redis")
return True
@staticmethod
- def _publish_stream_switch_event(channel_id, new_url, user_agent=None, stream_id=None):
+ def _publish_stream_switch_event(channel_id, new_url, user_agent=None, stream_id=None, m3u_profile_id=None):
"""Publish a stream switch event to Redis pubsub"""
proxy_server = ProxyServer.get_instance()
@@ -465,6 +471,7 @@ class ChannelService:
"url": new_url,
"user_agent": user_agent,
"stream_id": stream_id,
+ "m3u_profile_id": m3u_profile_id,
"requester": proxy_server.worker_id,
"timestamp": time.time()
}
diff --git a/apps/proxy/ts_proxy/views.py b/apps/proxy/ts_proxy/views.py
index d71da8d4..87a8e51b 100644
--- a/apps/proxy/ts_proxy/views.py
+++ b/apps/proxy/ts_proxy/views.py
@@ -351,6 +351,7 @@ def change_stream(request, channel_id):
# Use the info from the stream
new_url = stream_info['url']
user_agent = stream_info['user_agent']
+ m3u_profile_id = stream_info.get('m3u_profile_id')
# Stream ID will be passed to change_stream_url later
elif not new_url:
return JsonResponse({'error': 'Either url or stream_id must be provided'}, status=400)
@@ -359,7 +360,7 @@ def change_stream(request, channel_id):
# Use the service layer instead of direct implementation
# Pass stream_id to ensure proper connection tracking
- result = ChannelService.change_stream_url(channel_id, new_url, user_agent, stream_id)
+ result = ChannelService.change_stream_url(channel_id, new_url, user_agent, stream_id, m3u_profile_id)
# Get the stream manager before updating URL
stream_manager = proxy_server.stream_managers.get(channel_id)
diff --git a/frontend/src/pages/Stats.jsx b/frontend/src/pages/Stats.jsx
index e2e90a7d..ef15d605 100644
--- a/frontend/src/pages/Stats.jsx
+++ b/frontend/src/pages/Stats.jsx
@@ -82,12 +82,23 @@ const ChannelCard = ({ channel, clients, stopClient, stopChannel, logos, channel
const [availableStreams, setAvailableStreams] = useState([]);
const [isLoadingStreams, setIsLoadingStreams] = useState(false);
const [activeStreamId, setActiveStreamId] = useState(null);
+ const [currentM3UProfile, setCurrentM3UProfile] = useState(null); // Add state for current M3U profile
// Safety check - if channel doesn't have required data, don't render
if (!channel || !channel.channel_id) {
return null;
}
+ // Update M3U profile information when channel data changes
+ useEffect(() => {
+ // If the channel data includes M3U profile information, update our state
+ if (channel.m3u_profile || channel.m3u_profile_name) {
+ setCurrentM3UProfile({
+ name: channel.m3u_profile?.name || channel.m3u_profile_name || 'Default M3U'
+ });
+ }
+ }, [channel.m3u_profile, channel.m3u_profile_name, channel.stream_id]);
+
// Fetch available streams for this channel
useEffect(() => {
const fetchStreams = async () => {
@@ -110,6 +121,11 @@ const ChannelCard = ({ channel, clients, stopClient, stopChannel, logos, channel
if (matchingStream) {
setActiveStreamId(matchingStream.id.toString());
+
+ // If the stream has M3U profile info, save it
+ if (matchingStream.m3u_profile) {
+ setCurrentM3UProfile(matchingStream.m3u_profile);
+ }
}
}
}
@@ -138,6 +154,14 @@ const ChannelCard = ({ channel, clients, stopClient, stopChannel, logos, channel
// Update the local active stream ID immediately
setActiveStreamId(streamId);
+ // Update M3U profile information if available in the response
+ if (response && response.m3u_profile) {
+ setCurrentM3UProfile(response.m3u_profile);
+ } else if (selectedStream && selectedStream.m3u_profile) {
+ // Fallback to the profile from the selected stream
+ setCurrentM3UProfile(selectedStream.m3u_profile);
+ }
+
// Show detailed notification with stream name
notifications.show({
title: 'Stream switching',
@@ -152,6 +176,12 @@ const ChannelCard = ({ channel, clients, stopClient, stopChannel, logos, channel
if (channelId) {
const updatedStreamData = await API.getChannelStreams(channelId);
console.log("Channel streams after switch:", updatedStreamData);
+
+ // Update current stream information with fresh data
+ const updatedStream = updatedStreamData.find(s => s.id.toString() === streamId);
+ if (updatedStream && updatedStream.m3u_profile) {
+ setCurrentM3UProfile(updatedStream.m3u_profile);
+ }
}
} catch (error) {
console.error("Error checking streams after switch:", error);
@@ -305,6 +335,12 @@ const ChannelCard = ({ channel, clients, stopClient, stopChannel, logos, channel
const avgBitrate = channel.avg_bitrate || '0 Kbps';
const streamProfileName = channel.stream_profile?.name || 'Unknown Profile';
+ // Use currentM3UProfile if available, otherwise fall back to channel data
+ const m3uProfileName = currentM3UProfile?.name ||
+ channel.m3u_profile?.name ||
+ channel.m3u_profile_name ||
+ 'Default M3U';
+
// Create select options for available streams
const streamOptions = availableStreams.map(stream => ({
value: stream.id.toString(),
@@ -377,6 +413,16 @@ const ChannelCard = ({ channel, clients, stopClient, stopChannel, logos, channel
+ {/* Display M3U profile information */}
+
+
+
+
+ {m3uProfileName}
+
+
+
+
{/* Add stream selection dropdown */}
{availableStreams.length > 0 && (