| 98 | @api_view(["GET"]) |
| 99 | @permission_classes([AllowAny]) |
| 100 | def stream_ts(request, channel_id, user=None, force_output_format=None): |
| 101 | if not network_access_allowed(request, "STREAMS"): |
| 102 | return JsonResponse({"error": "Forbidden"}, status=403) |
| 103 | |
| 104 | """Stream TS data to client with immediate response and keep-alive packets during initialization""" |
| 105 | if user is None and hasattr(request, 'user') and request.user.is_authenticated: |
| 106 | user = request.user |
| 107 | |
| 108 | channel = get_stream_object(channel_id) |
| 109 | |
| 110 | client_user_agent = None |
| 111 | proxy_server = ProxyServer.get_instance() |
| 112 | connection_allocated = False # Track if connection slot was allocated via get_stream() |
| 113 | |
| 114 | try: |
| 115 | # Generate a unique client ID |
| 116 | client_id = f"client_{int(time.time() * 1000)}_{random.randint(1000, 9999)}" |
| 117 | client_ip = get_client_ip(request) |
| 118 | logger.info(f"[{client_id}] Requested stream for channel {channel_id}") |
| 119 | |
| 120 | # Extract client user agent early |
| 121 | for header in ["HTTP_USER_AGENT", "User-Agent", "user-agent"]: |
| 122 | if header in request.META: |
| 123 | client_user_agent = request.META[header] |
| 124 | logger.debug( |
| 125 | f"[{client_id}] Client connected with user agent: {client_user_agent}" |
| 126 | ) |
| 127 | break |
| 128 | |
| 129 | if user: |
| 130 | if not check_user_stream_limits(user, client_id, media_id=channel_id): |
| 131 | return JsonResponse( |
| 132 | {"error": f"Stream limit exceeded ({user.stream_limit} concurrent streams allowed)"}, |
| 133 | status=429 |
| 134 | ) |
| 135 | |
| 136 | if ChannelService.is_channel_unavailable_for_new_clients(channel_id): |
| 137 | logger.info( |
| 138 | f"[{client_id}] Channel {channel_id} unavailable. Teardown or pending shutdown" |
| 139 | ) |
| 140 | return _channel_stopping_response() |
| 141 | |
| 142 | # Check if we need to reinitialize the channel |
| 143 | needs_initialization = True |
| 144 | channel_state = None |
| 145 | channel_initializing = False |
| 146 | |
| 147 | # Get current channel state from Redis if available |
| 148 | if proxy_server.redis_client: |
| 149 | metadata_key = RedisKeys.channel_metadata(channel_id) |
| 150 | if proxy_server.redis_client.exists(metadata_key): |
| 151 | metadata = proxy_server.redis_client.hgetall(metadata_key) |
| 152 | state_field = ChannelMetadataField.STATE |
| 153 | if state_field in metadata: |
| 154 | channel_state = metadata[state_field] |
| 155 | |
| 156 | # Active/running states - channel is operational, don't reinitialize |
| 157 | if channel_state in [ |