Adds current m3u profile to stats.

This commit is contained in:
SergeantPanda 2025-04-27 14:43:09 -05:00
parent d3a7dbca10
commit 88f27d62f1
4 changed files with 102 additions and 10 deletions

View file

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

View file

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

View file

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

View file

@ -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
</Group>
</Flex>
{/* Display M3U profile information */}
<Flex justify="flex-end" align="center" mt={-8}>
<Group gap={5}>
<HardDriveUpload size="18" />
<Tooltip label="Current M3U Profile">
<Text size="xs">{m3uProfileName}</Text>
</Tooltip>
</Group>
</Flex>
{/* Add stream selection dropdown */}
{availableStreams.length > 0 && (
<Tooltip label="Switch to another stream source">