Mythus commited on
Commit
918d24b
·
verified ·
1 Parent(s): 255b560

Upload 25 files

Browse files
Files changed (4) hide show
  1. Dockerfile +5 -3
  2. LICENSE +21 -0
  3. README.md +215 -13
  4. mediaflow_proxy/handlers.py +65 -40
Dockerfile CHANGED
@@ -3,7 +3,7 @@ FROM python:3.12-slim
3
  # Set environment variables
4
  ENV PYTHONDONTWRITEBYTECODE="1"
5
  ENV PYTHONUNBUFFERED="1"
6
- ENV PORT="8080"
7
 
8
  # Set work directory
9
  WORKDIR /mediaflow_proxy
@@ -22,6 +22,8 @@ USER mediaflow_proxy
22
  RUN pip install --user --no-cache-dir poetry
23
 
24
  # Copy only requirements to cache them in docker layer
 
 
25
  # Project initialization:
26
  RUN poetry config virtualenvs.in-project true \
27
  && poetry install --no-interaction --no-ansi --no-dev
@@ -30,7 +32,7 @@ RUN poetry config virtualenvs.in-project true \
30
  COPY --chown=mediaflow_proxy:mediaflow_proxy . /mediaflow_proxy
31
 
32
  # Expose the port the app runs on
33
- EXPOSE 8080
34
 
35
  # Activate virtual environment and run the application with Gunicorn
36
- CMD ["poetry", "run", "gunicorn", "mediaflow_proxy.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8080", "--timeout", "120", "--max-requests", "500", "--max-requests-jitter", "200"]
 
3
  # Set environment variables
4
  ENV PYTHONDONTWRITEBYTECODE="1"
5
  ENV PYTHONUNBUFFERED="1"
6
+ ENV PORT="8888"
7
 
8
  # Set work directory
9
  WORKDIR /mediaflow_proxy
 
22
  RUN pip install --user --no-cache-dir poetry
23
 
24
  # Copy only requirements to cache them in docker layer
25
+ COPY --chown=mediaflow_proxy:mediaflow_proxy pyproject.toml poetry.lock* /mediaflow_proxy/
26
+
27
  # Project initialization:
28
  RUN poetry config virtualenvs.in-project true \
29
  && poetry install --no-interaction --no-ansi --no-dev
 
32
  COPY --chown=mediaflow_proxy:mediaflow_proxy . /mediaflow_proxy
33
 
34
  # Expose the port the app runs on
35
+ EXPOSE 8888
36
 
37
  # Activate virtual environment and run the application with Gunicorn
38
+ CMD ["poetry", "run", "gunicorn", "mediaflow_proxy.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8888", "--timeout", "120", "--max-requests", "500", "--max-requests-jitter", "200"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) [2024] [Mohamed Zumair]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,13 +1,215 @@
1
- ---
2
- title: MediaProxy
3
- emoji: 💻
4
- colorFrom: indigo
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- app_port: 8080
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
12
-
13
- Test
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MediaFlow Proxy
2
+
3
+ <div style="text-align: center;">
4
+ <img src="https://cdn.githubraw.com/mhdzumair/mediaflow-proxy/main/static/logo.png" alt="MediaFlow Proxy Logo" width="200" style="border-radius: 15px;">
5
+ </div>
6
+
7
+ MediaFlow Proxy is a powerful and flexible solution for proxifying various types of media streams. It supports HTTP(S) links, HLS (M3U8) streams, and MPEG-DASH streams, including DRM-protected content. This proxy can convert MPEG-DASH DRM-protected streams to decrypted HLS live streams in real-time, making it one of the fastest live decrypter servers available.
8
+
9
+ ## Features
10
+
11
+ - Convert MPEG-DASH streams (DRM-protected and non-protected) to HLS
12
+ - Support for Clear Key DRM-protected MPD DASH streams
13
+ - Support for non-DRM protected DASH live and VOD streams
14
+ - Proxy HTTP/HTTPS links with custom headers
15
+ - Proxy and modify HLS (M3U8) streams in real-time with custom headers and key URL modifications for bypassing some sneaky restrictions.
16
+ - Retrieve public IP address of the MediaFlow Proxy server for use with Debrid services
17
+ - Support for HTTP/HTTPS/SOCKS5 proxy forwarding
18
+ - Protect against unauthorized access and network bandwidth abuses
19
+
20
+ ## Installation
21
+
22
+
23
+ ### Option 1: Self-Hosted Deployment
24
+
25
+ #### Using Docker from Docker Hub
26
+
27
+ 1. Pull & Run the Docker image:
28
+ ```
29
+ docker run -p 8888:8888 -e API_PASSWORD=your_password mhdzumair/mediaflow-proxy
30
+ ```
31
+
32
+ #### Using Poetry
33
+
34
+ 1. Clone the repository:
35
+ ```
36
+ git clone https://github.com/mhdzumair/mediaflow-proxy.git
37
+ cd mediaflow-proxy
38
+ ```
39
+
40
+ 2. Install dependencies using Poetry:
41
+ ```
42
+ poetry install
43
+ ```
44
+
45
+ 3. Set the `API_PASSWORD` environment variable in `.env`:
46
+ ```
47
+ echo "API_PASSWORD=your_password" > .env
48
+ ```
49
+
50
+ 4. Run the FastAPI server:
51
+ ```
52
+ poetry run uvicorn mediaflow_proxy.main:app --host 0.0.0.0 --port 8888
53
+ ```
54
+
55
+
56
+ #### Build and Run Docker Image Locally
57
+
58
+ 1. Build the Docker image:
59
+ ```
60
+ docker build -t mediaflow-proxy .
61
+ ```
62
+
63
+ 2. Run the Docker container:
64
+ ```
65
+ docker run -p 8888:8888 -e API_PASSWORD=your_password mediaflow-proxy
66
+ ```
67
+
68
+ #### Configuration
69
+
70
+ Set the following environment variables:
71
+
72
+ - `API_PASSWORD`: Required. Protects against unauthorized access and API network abuses.
73
+ - `PROXY_URL`: Optional. HTTP/HTTPS/SOCKS5 proxy URL for forwarding network requests.
74
+ - `MPD_LIVE_STREAM_DELAY`: Optional. Delay in seconds for live DASH streams. This is useful to prevent buffering issues with live streams. Default is `30` seconds.
75
+
76
+
77
+ ### Option 2: Premium Hosted Service (ElfHosted)
78
+ <div style="text-align: center;">
79
+ <img src="https://store.elfhosted.com/wp-content/uploads/2024/08/mediaflow-proxy.jpg" alt="ElfHosted Logo" width="200" style="border-radius: 15px;">
80
+ </div>
81
+ For a hassle-free, high-performance deployment of MediaFlow Proxy, consider the premium hosted service through ElfHosted.
82
+
83
+ To purchase:
84
+ 1. Visit [https://store.elfhosted.com/product/mediaflow-proxy](https://store.elfhosted.com/product/mediaflow-proxy)
85
+ 2. Follow ElfHosted's setup instructions
86
+
87
+ Benefits:
88
+ - Instant setup and automatic updates
89
+ - High performance and 24/7 availability
90
+ - No server maintenance required
91
+
92
+ Ideal for users who want a reliable, plug-and-play solution without the technical overhead of self-hosting.
93
+
94
+ ## Usage
95
+
96
+ ### Endpoints
97
+
98
+ 1. `/proxy/hls`: Proxify HLS streams
99
+ 2. `/proxy/stream`: Proxy generic http video streams
100
+ 3. `/proxy/mpd/manifest`: Process MPD manifests
101
+ 4. `/proxy/mpd/playlist`: Generate HLS playlists from MPD
102
+ 5. `/proxy/mpd/segment`: Process and decrypt media segments
103
+ 6. `/proxy/ip`: Get the public IP address of the MediaFlow Proxy server
104
+
105
+ Once the server is running, for more details on the available endpoints and their parameters, visit the Swagger UI at `http://localhost:8888/docs`.
106
+
107
+ ### Examples
108
+
109
+ #### Proxy HTTPS Stream
110
+
111
+ ```bash
112
+ mpv "http://localhost:8888/proxy/stream?d=https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/sample-mp4-file.mp4&api_password=your_password"
113
+ ```
114
+
115
+ #### Proxy HLS Stream with Headers
116
+
117
+ ```bash
118
+ mpv "http://localhost:8888/proxy/hls?d=https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8&h_referer=https://apple.com/&h_origin=https://apple.com&h_user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36&api_password=your_password"
119
+ ```
120
+
121
+ #### Live DASH Stream (Non-DRM Protected)
122
+
123
+ ```bash
124
+ mpv -v "http://localhost:8888/proxy/mpd/manifest?d=https://livesim.dashif.org/livesim/chunkdur_1/ato_7/testpic4_8s/Manifest.mpd&api_password=your_password"
125
+ ```
126
+
127
+ #### VOD DASH Stream (DRM Protected)
128
+
129
+ ```bash
130
+ mpv -v "http://localhost:8888/proxy/mpd/manifest?d=https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd&key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=your_password"
131
+ ```
132
+
133
+ Note: The `key` and `key_id` parameters are automatically processed if they're not in the correct format.
134
+
135
+ ### URL Encoding
136
+
137
+ For players like VLC that require properly encoded URLs, use the `encode_mediaflow_proxy_url` function:
138
+
139
+ ```python
140
+ from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url
141
+
142
+ encoded_url = encode_mediaflow_proxy_url(
143
+ mediaflow_proxy_url="http://127.0.0.1:8888",
144
+ endpoint="/proxy/mpd/manifest",
145
+ destination_url="https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd",
146
+ query_params={
147
+ "key_id": "nrQFDeRLSAKTLifXUIPiZg",
148
+ "key": "FmY0xnWCPCNaSpRG-tUuTQ",
149
+ "api_password": "your_password"
150
+ },
151
+ request_headers={
152
+ "referer": "https://media.axprod.net/",
153
+ "origin": "https://media.axprod.net",
154
+ }
155
+ )
156
+
157
+ print(encoded_url)
158
+
159
+ # http://127.0.0.1:8888/proxy/mpd/manifest?key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=your_password&d=https%3A%2F%2Fmedia.axprod.net%2FTestVectors%2Fv7-MultiDRM-SingleKey%2FManifest_1080p_ClearKey.mpd&h_referer=https%3A%2F%2Fmedia.axprod.net%2F&h_origin=https%3A%2F%2Fmedia.axprod.net
160
+ ```
161
+
162
+ This will output a properly encoded URL that can be used with players like VLC.
163
+
164
+ ```bash
165
+ vlc "http://127.0.0.1:8888/proxy/mpd/manifest?key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=dedsec&d=https%3A%2F%2Fmedia.axprod.net%2FTestVectors%2Fv7-MultiDRM-SingleKey%2FManifest_1080p_ClearKey.mpd"
166
+ ```
167
+
168
+ ### Using MediaFlow Proxy with Debrid Services and Stremio Addons
169
+
170
+ MediaFlow Proxy can be particularly useful when working with Debrid services (like Real-Debrid, AllDebrid) and Stremio addons. The `/proxy/ip` endpoint allows you to retrieve the public IP address of the MediaFlow Proxy server, which is crucial for routing Debrid streams correctly.
171
+
172
+ When a Stremio addon needs to create a video URL for a Debrid service, it typically needs to provide the user's public IP address. However, when routing the Debrid stream through MediaFlow Proxy, you should use the IP address of the MediaFlow Proxy server instead.
173
+
174
+ Here's how to utilize MediaFlow Proxy in this scenario:
175
+
176
+ 1. If MediaFlow Proxy is accessible over the internet:
177
+ - Use the `/proxy/ip` endpoint to get the MediaFlow Proxy server's public IP.
178
+ - Use this IP when creating Debrid service URLs in your Stremio addon.
179
+
180
+ 2. If MediaFlow Proxy is set up locally:
181
+ - Stremio addons can directly use the client's IP address.
182
+
183
+
184
+ ## Future Development
185
+
186
+ - Add support for Widevine and PlayReady decryption
187
+
188
+ ## Acknowledgements and Inspirations
189
+
190
+ MediaFlow Proxy was developed with inspiration from various projects and resources:
191
+
192
+ - [Stremio Server](https://github.com/Stremio/stremio-server) for HLS Proxify implementation, which inspired our HLS M3u8 Manifest parsing and redirection proxify support.
193
+ - [Comet Debrid proxy](https://github.com/g0ldyy/comet) for the idea of proxifying HTTPS video streams.
194
+ - [mp4decrypt](https://www.bento4.com/developers/dash/encryption_and_drm/), [mp4box](https://wiki.gpac.io/xmlformats/Common-Encryption/), and [devine](https://github.com/devine-dl/devine) for insights on parsing MPD and decrypting Clear Key DRM protected content.
195
+ - Test URLs were sourced from:
196
+ - [OTTVerse MPEG-DASH MPD Examples](https://ottverse.com/free-mpeg-dash-mpd-manifest-example-test-urls/)
197
+ - [OTTVerse HLS M3U8 Examples](https://ottverse.com/free-hls-m3u8-test-urls/)
198
+ - [Bitmovin Stream Test](https://bitmovin.com/demos/stream-test)
199
+ - [Bitmovin DRM Demo](https://bitmovin.com/demos/drm)
200
+ - [DASH-IF Reference Player](http://reference.dashif.org/dash.js/nightly/samples/)
201
+ - [HLS Protocol RFC](https://www.rfc-editor.org/rfc/rfc8216) for understanding the HLS protocol specifications.
202
+ - Claude 3.5 Sonnet for code assistance and brainstorming.
203
+
204
+ ## Contributing
205
+
206
+ Contributions are welcome! Please feel free to submit a Pull Request.
207
+
208
+ ## License
209
+
210
+ [MIT License](LICENSE)
211
+
212
+
213
+ ## Disclaimer
214
+
215
+ This project is for educational purposes only. The developers of MediaFlow Proxy are not responsible for any misuse of this software. Please ensure that you have the necessary permissions to access and use the media streams you are proxying.
mediaflow_proxy/handlers.py CHANGED
@@ -31,17 +31,43 @@ async def handle_hls_stream_proxy(request: Request, destination: str, headers: d
31
  Returns:
32
  Response: The HTTP response with the processed m3u8 playlist or streamed content.
33
  """
 
 
 
 
 
 
 
34
  try:
35
- if destination.endswith((".m3u", ".m3u8")) or "mpegurl" in headers.get("accept", "").lower():
36
- return await fetch_and_process_m3u8(destination, headers, request, key_url)
37
-
38
- return await handle_stream_request(request.method, destination, headers)
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  except httpx.HTTPStatusError as e:
40
- logger.error(f"HTTP error while fetching m3u8: {e}")
41
- return Response(status_code=e.response.status_code, content=str(e))
 
 
 
 
 
42
  except Exception as e:
43
- logger.exception(f"Error in live_stream_proxy: {str(e)}")
44
- return Response(status_code=500, content=f"Internal server error: {str(e)}")
 
45
 
46
 
47
  async def proxy_stream(method: str, video_url: str, headers: dict):
@@ -90,23 +116,27 @@ async def handle_stream_request(method: str, video_url: str, headers: dict):
90
  background=BackgroundTask(streamer.close),
91
  )
92
  except httpx.HTTPStatusError as e:
93
- logger.error(f"Upstream service error while handling {method} request: {e}")
94
  await client.aclose()
 
95
  return Response(status_code=e.response.status_code, content=f"Upstream service error: {e}")
96
  except DownloadError as e:
 
97
  logger.error(f"Error downloading {video_url}: {e}")
98
- return Response(status_code=502, content=str(e))
99
  except Exception as e:
100
- logger.error(f"Internal server error while handling {method} request: {e}")
101
  await client.aclose()
 
102
  return Response(status_code=502, content=f"Internal server error: {e}")
103
 
104
 
105
- async def fetch_and_process_m3u8(url: str, headers: dict, request: Request, key_url: HttpUrl = None):
 
 
106
  """
107
  Fetches and processes the m3u8 playlist, converting it to an HLS playlist.
108
 
109
  Args:
 
110
  url (str): The URL of the m3u8 playlist.
111
  headers (dict): The headers to include in the request.
112
  request (Request): The incoming HTTP request.
@@ -115,34 +145,29 @@ async def fetch_and_process_m3u8(url: str, headers: dict, request: Request, key_
115
  Returns:
116
  Response: The HTTP response with the processed m3u8 playlist.
117
  """
118
- async with httpx.AsyncClient(
119
- follow_redirects=True,
120
- timeout=httpx.Timeout(30.0),
121
- limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
122
- proxy=settings.proxy_url,
123
- ) as client:
124
- try:
125
- streamer = Streamer(client)
126
- content = await streamer.get_text(url, headers)
127
- processor = M3U8Processor(request, key_url)
128
- processed_content = await processor.process_m3u8(content, str(streamer.response.url))
129
- return Response(
130
- content=processed_content,
131
- media_type="application/vnd.apple.mpegurl",
132
- headers={
133
- "Content-Disposition": "inline",
134
- "Accept-Ranges": "none",
135
- },
136
- )
137
- except httpx.HTTPStatusError as e:
138
- logger.error(f"HTTP error while fetching m3u8: {e}")
139
- return Response(status_code=e.response.status_code, content=str(e))
140
- except DownloadError as e:
141
- logger.error(f"Error downloading m3u8: {url}")
142
- return Response(status_code=502, content=str(e))
143
- except Exception as e:
144
- logger.exception(f"Unexpected error while processing m3u8: {e}")
145
- return Response(status_code=502, content=str(e))
146
 
147
 
148
  async def handle_drm_key_data(key_id, key, drm_info):
 
31
  Returns:
32
  Response: The HTTP response with the processed m3u8 playlist or streamed content.
33
  """
34
+ client = httpx.AsyncClient(
35
+ follow_redirects=True,
36
+ timeout=httpx.Timeout(30.0),
37
+ limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
38
+ proxy=settings.proxy_url,
39
+ )
40
+ streamer = Streamer(client)
41
  try:
42
+ if destination.endswith((".m3u", ".m3u8")):
43
+ return await fetch_and_process_m3u8(streamer, destination, headers, request, key_url)
44
+
45
+ response = await streamer.head(destination, headers)
46
+ if "mpegurl" in response.headers.get("content-type", "").lower():
47
+ return await fetch_and_process_m3u8(streamer, destination, headers, request, key_url)
48
+
49
+ headers.update({"accept-ranges": headers.get("range", "bytes=0-")})
50
+ # handle the encoding response header, since decompression is handled by the httpx
51
+ if "content-encoding" in response.headers:
52
+ del response.headers["content-encoding"]
53
+
54
+ return StreamingResponse(
55
+ streamer.stream_content(destination, headers),
56
+ headers=response.headers,
57
+ background=BackgroundTask(streamer.close),
58
+ )
59
  except httpx.HTTPStatusError as e:
60
+ await client.aclose()
61
+ logger.error(f"Upstream service error while handling request: {e}")
62
+ return Response(status_code=e.response.status_code, content=f"Upstream service error: {e}")
63
+ except DownloadError as e:
64
+ await client.aclose()
65
+ logger.error(f"Error downloading {destination}: {e}")
66
+ return Response(status_code=e.status_code, content=str(e))
67
  except Exception as e:
68
+ await client.aclose()
69
+ logger.error(f"Internal server error while handling request: {e}")
70
+ return Response(status_code=502, content=f"Internal server error: {e}")
71
 
72
 
73
  async def proxy_stream(method: str, video_url: str, headers: dict):
 
116
  background=BackgroundTask(streamer.close),
117
  )
118
  except httpx.HTTPStatusError as e:
 
119
  await client.aclose()
120
+ logger.error(f"Upstream service error while handling {method} request: {e}")
121
  return Response(status_code=e.response.status_code, content=f"Upstream service error: {e}")
122
  except DownloadError as e:
123
+ await client.aclose()
124
  logger.error(f"Error downloading {video_url}: {e}")
125
+ return Response(status_code=e.status_code, content=str(e))
126
  except Exception as e:
 
127
  await client.aclose()
128
+ logger.error(f"Internal server error while handling {method} request: {e}")
129
  return Response(status_code=502, content=f"Internal server error: {e}")
130
 
131
 
132
+ async def fetch_and_process_m3u8(
133
+ streamer: Streamer, url: str, headers: dict, request: Request, key_url: HttpUrl = None
134
+ ):
135
  """
136
  Fetches and processes the m3u8 playlist, converting it to an HLS playlist.
137
 
138
  Args:
139
+ streamer (Streamer): The HTTP client to use for streaming.
140
  url (str): The URL of the m3u8 playlist.
141
  headers (dict): The headers to include in the request.
142
  request (Request): The incoming HTTP request.
 
145
  Returns:
146
  Response: The HTTP response with the processed m3u8 playlist.
147
  """
148
+ try:
149
+ content = await streamer.get_text(url, headers)
150
+ processor = M3U8Processor(request, key_url)
151
+ processed_content = await processor.process_m3u8(content, str(streamer.response.url))
152
+ return Response(
153
+ content=processed_content,
154
+ media_type="application/vnd.apple.mpegurl",
155
+ headers={
156
+ "Content-Disposition": "inline",
157
+ "Accept-Ranges": "none",
158
+ },
159
+ )
160
+ except httpx.HTTPStatusError as e:
161
+ logger.error(f"HTTP error while fetching m3u8: {e}")
162
+ return Response(status_code=e.response.status_code, content=str(e))
163
+ except DownloadError as e:
164
+ logger.error(f"Error downloading m3u8: {url}")
165
+ return Response(status_code=502, content=str(e))
166
+ except Exception as e:
167
+ logger.exception(f"Unexpected error while processing m3u8: {e}")
168
+ return Response(status_code=502, content=str(e))
169
+ finally:
170
+ await streamer.close()
 
 
 
 
 
171
 
172
 
173
  async def handle_drm_key_data(key_id, key, drm_info):