From 7812a410b3dd78e09769a7e4fc366cd26942791c Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Tue, 10 Jun 2025 21:17:30 -0500 Subject: [PATCH] Allow users to change proxy settings. --- apps/proxy/ts_proxy/config_helper.py | 46 ++++++++++++++++++++- core/api_views.py | 62 +++++++++++++++++++++++++++- core/models.py | 38 +++++++++++++++++ core/serializers.py | 42 ++++++++++++++++++- 4 files changed, 183 insertions(+), 5 deletions(-) diff --git a/apps/proxy/ts_proxy/config_helper.py b/apps/proxy/ts_proxy/config_helper.py index 4057a2d5..28474b37 100644 --- a/apps/proxy/ts_proxy/config_helper.py +++ b/apps/proxy/ts_proxy/config_helper.py @@ -34,6 +34,13 @@ class ConfigHelper: @staticmethod def channel_shutdown_delay(): """Get channel shutdown delay in seconds""" + try: + from core.models import ProxySettings + settings = ProxySettings.objects.first() + if settings: + return settings.channel_shutdown_delay + except: + pass return ConfigHelper.get('CHANNEL_SHUTDOWN_DELAY', 0) @staticmethod @@ -54,6 +61,13 @@ class ConfigHelper: @staticmethod def redis_chunk_ttl(): """Get Redis chunk TTL in seconds""" + try: + from core.models import ProxySettings + settings = ProxySettings.objects.first() + if settings: + return settings.redis_chunk_ttl + except: + pass return ConfigHelper.get('REDIS_CHUNK_TTL', 60) @staticmethod @@ -85,11 +99,39 @@ class ConfigHelper: def failover_grace_period(): """Get extra time (in seconds) to allow for stream switching before disconnecting clients""" return ConfigHelper.get('FAILOVER_GRACE_PERIOD', 20) # Default to 20 seconds + @staticmethod def buffering_timeout(): """Get buffering timeout in seconds""" + try: + from core.models import ProxySettings + settings = ProxySettings.objects.first() + if settings: + return settings.buffering_timeout + except: + pass return ConfigHelper.get('BUFFERING_TIMEOUT', 15) # Default to 15 seconds + @staticmethod def buffering_speed(): - """Get buffering speed in bytes per second""" - return ConfigHelper.get('BUFFERING_SPEED',1) # Default to 1x + """Get buffering speed threshold""" + try: + from core.models import ProxySettings + settings = ProxySettings.objects.first() + if settings: + return settings.buffering_speed + except: + pass + return ConfigHelper.get('BUFFERING_SPEED', 1) # Default to 1x + + @staticmethod + def channel_init_grace_period(): + """Get channel initialization grace period in seconds""" + try: + from core.models import ProxySettings + settings = ProxySettings.objects.first() + if settings: + return settings.channel_init_grace_period + except: + pass + return ConfigHelper.get('CHANNEL_INIT_GRACE_PERIOD', 5) # Default to 5 seconds diff --git a/core/api_views.py b/core/api_views.py index 77473b5d..84eb4918 100644 --- a/core/api_views.py +++ b/core/api_views.py @@ -1,10 +1,11 @@ # core/api_views.py from rest_framework import viewsets, status +from rest_framework.decorators import action from rest_framework.response import Response from django.shortcuts import get_object_or_404 -from .models import UserAgent, StreamProfile, CoreSettings, STREAM_HASH_KEY -from .serializers import UserAgentSerializer, StreamProfileSerializer, CoreSettingsSerializer +from .models import UserAgent, StreamProfile, CoreSettings, ProxySettings, STREAM_HASH_KEY +from .serializers import UserAgentSerializer, StreamProfileSerializer, CoreSettingsSerializer, ProxySettingsSerializer from rest_framework.permissions import IsAuthenticated from rest_framework.decorators import api_view, permission_classes from drf_yasg.utils import swagger_auto_schema @@ -44,6 +45,63 @@ class CoreSettingsViewSet(viewsets.ModelViewSet): return response +class ProxySettingsViewSet(viewsets.ModelViewSet): + """ + API endpoint for proxy settings. + This is treated as a singleton: only one instance should exist. + """ + serializer_class = ProxySettingsSerializer + + def get_queryset(self): + # Always return the singleton settings + return ProxySettings.objects.all() + + def get_object(self): + # Always return the singleton settings (create if doesn't exist) + return ProxySettings.get_settings() + + def list(self, request, *args, **kwargs): + # Return the singleton settings as a single object + settings = self.get_object() + serializer = self.get_serializer(settings) + return Response(serializer.data) + + def retrieve(self, request, *args, **kwargs): + # Always return the singleton settings regardless of ID + settings = self.get_object() + serializer = self.get_serializer(settings) + return Response(serializer.data) + + def update(self, request, *args, **kwargs): + # Update the singleton settings + settings = self.get_object() + serializer = self.get_serializer(settings, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + + def partial_update(self, request, *args, **kwargs): + return self.update(request, *args, **kwargs) + + @action(detail=False, methods=['get', 'patch']) + def settings(self, request): + """ + Get or update the proxy settings. + """ + settings = self.get_object() + + if request.method == 'GET': + # Return current settings + serializer = self.get_serializer(settings) + return Response(serializer.data) + + elif request.method == 'PATCH': + # Update settings + serializer = self.get_serializer(settings, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + @swagger_auto_schema( method='get', operation_description="Endpoint for environment details", diff --git a/core/models.py b/core/models.py index fe7e9eb5..3af21628 100644 --- a/core/models.py +++ b/core/models.py @@ -183,3 +183,41 @@ class CoreSettings(models.Model): return cls.objects.get(key=AUTO_IMPORT_MAPPED_FILES).value except cls.DoesNotExist: return None + +class ProxySettings(models.Model): + """Proxy configuration settings""" + + buffering_timeout = models.IntegerField( + default=15, + help_text="Seconds to wait for buffering before switching streams" + ) + + buffering_speed = models.FloatField( + default=1.0, + help_text="Speed threshold to consider stream buffering (1.0 = normal speed)" + ) + + redis_chunk_ttl = models.IntegerField( + default=60, + help_text="Time in seconds before Redis chunks expire" + ) + + channel_shutdown_delay = models.IntegerField( + default=0, + help_text="Seconds to wait after last client before shutting down channel" + ) + + channel_init_grace_period = models.IntegerField( + default=5, + help_text="Seconds to wait for first client after channel initialization" + ) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Proxy Settings" + verbose_name_plural = "Proxy Settings" + + def __str__(self): + return "Proxy Settings" diff --git a/core/serializers.py b/core/serializers.py index c80ad630..4648a74a 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -1,7 +1,7 @@ # core/serializers.py from rest_framework import serializers -from .models import UserAgent, StreamProfile, CoreSettings +from .models import CoreSettings, UserAgent, StreamProfile, ProxySettings class UserAgentSerializer(serializers.ModelSerializer): class Meta: @@ -17,3 +17,43 @@ class CoreSettingsSerializer(serializers.ModelSerializer): class Meta: model = CoreSettings fields = '__all__' + +class ProxySettingsSerializer(serializers.ModelSerializer): + class Meta: + model = ProxySettings + fields = [ + 'id', + 'buffering_timeout', + 'buffering_speed', + 'redis_chunk_ttl', + 'channel_shutdown_delay', + 'channel_init_grace_period', + 'created_at', + 'updated_at' + ] + read_only_fields = ['id', 'created_at', 'updated_at'] + + def validate_buffering_timeout(self, value): + if value < 1 or value > 300: + raise serializers.ValidationError("Buffering timeout must be between 1 and 300 seconds") + return value + + def validate_buffering_speed(self, value): + if value < 0.1 or value > 10.0: + raise serializers.ValidationError("Buffering speed must be between 0.1 and 10.0") + return value + + def validate_redis_chunk_ttl(self, value): + if value < 10 or value > 3600: + raise serializers.ValidationError("Redis chunk TTL must be between 10 and 3600 seconds") + return value + + def validate_channel_shutdown_delay(self, value): + if value < 0 or value > 300: + raise serializers.ValidationError("Channel shutdown delay must be between 0 and 300 seconds") + return value + + def validate_channel_init_grace_period(self, value): + if value < 1 or value > 60: + raise serializers.ValidationError("Channel init grace period must be between 1 and 60 seconds") + return value