Repository: streamlink/streamlink

Issue:
plugins.filmon: all channels fail to start. Debug show invalid server response
### Checklist

- [X] This is a plugin issue and not a different kind of issue
- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)
- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)

### Streamlink version

Latest stable release

### Description

I have tried diferent versions of streamlink including the latest. Ubuntu and windows.

When running streamlink.exe https://www.filmon.com/tv/itv1 low --player-external-http --player-external-http-port 23555 --player-continuous-http 

it says channel sleep and invaild server responce.

### Debug log

```text
C:\Program Files (x86)\Streamlink\bin>streamlink.exe https://www.filmon.com/tv/itv1 low --player-external-http --player-external-http-port 23555 --player-continuous-http --loglevel debug
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.9.8
[cli][debug] Streamlink: 3.1.1
[cli][debug] Requests(2.26.0), Socks(1.7.1), Websocket(1.2.1)
[cli][debug] Arguments:
[cli][debug]  url=https://www.filmon.com/tv/itv1
[cli][debug]  stream=['low']
[cli][debug]  --loglevel=debug
[cli][debug]  --player-continuous-http=True
[cli][debug]  --player-external-http=True
[cli][debug]  --player-external-http-port=23555
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files (x86)\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin filmon for URL https://www.filmon.com/tv/itv1
[plugins.filmon][debug] Found channel ID: 11
[plugins.filmon][debug] invalid server response
[plugins.filmon][debug] channel sleep 1
[plugins.filmon][debug] invalid server response
[plugins.filmon][debug] channel sleep 2
[plugins.filmon][debug] invalid server response
[plugins.filmon][debug] channel sleep 3
[plugins.filmon][debug] invalid server response
[plugins.filmon][debug] channel sleep 4
[plugins.filmon][debug] invalid server response
[plugins.filmon][debug] Reset cached channel: itv1
error: Unable to find 'self.api.channel' for 11
```



Discussion:
Hmm, 404 for:

http://www.filmon.com/api-v2/channel/{0}?protocol=hls

Looks to be available on:

http://www.filmon.com/tv/api/channel/{0}?protocol=hls

but that requires a session key for the API.

The website performs a POST to: https://www.filmon.com/ajax/getChannelInfo
```
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
```
with body:
```
channel_id=851&quality=low
```
and response:
```
{
  "id": 851,
  "logo": "https://static.filmon.com/assets/channels/851/logo.png?v2",
  "big_logo": "https://static.filmon.com/assets/channels/851/big_logo.png",
  "title": "5*",
  "alias": "5-star",
  "description": "5 star is an entertainment channel for people who like their drama sexier, their documentaries harder, their movies bigger and their soaps soapier. 5 Star is unpredictable, mischievous and relevant with exclusive new shows, blockbuster movies and US series. 5 Star is for people who like to be entertained....",
  "has_description": true,
  "group": "UK LIVE TV",
  "group_id": 5,
  "group_alias": "uk-live-tv",
  "type": "standard",
  "is_free": false,
  "is_free_sd_mode": false,
  "free_sd": false,
  "is_adult": false,
  "adult_content": false,
  "is_interactive": false,
  "is_vod": false,
  "is_vox": false,
  "has_tvguide": true,
  "recordable": true,
  "chat_keyword": "5-star",
  "created_at": 1341853217,
  "is360": false,
  "has360Sound": false,
  "jwplatform_media_id": "UrXk5bS4",
  "youtube_playlist_id": null,
  "preload_message": "Tuning your antenna",
  "preload_timeout": 3,
  "is_local": true,
  "preload_intro": {
    "name": "mp4:promo_short.mp4",
    "url": "rtmp://vod-static.la3.edge.filmon.com/static/"
  },
  "is_favorite": false,
  "images": {
    "logos": [
      <SNIPPED>
    ]
  },
  "extra_big_logo": "https://static.filmon.com/assets/channels/851/extra_big_logo.png",
  "schedule": true,
  "tvguide": [],
  "now_playing": null,
  "next_playing": null,
  "content_rating": "0",
  "watch_free_time": 30,
  "seekable": false,
  "serverside_record": false,
  "upnp_enabled": true,
  "expire_timeout": 30,
  "watch-timeout": "30",
  "expire_time": 1644002602,
  "subscriptions": {
    <SNIPPED>
  },
  "streams": [
    {
      "id": 1,
      "quality": "high",
      "url": "https://edge-823-ch-gv.filmon.com/live/851.high.stream/playlist.m3u8?id=0ad5aac39bb13fbe9156feccde25a24b9cc4d8e2b6e69fa51da411a8ed21acb01a34f53502dd935dbf6fa94c30881f48cb04e0769a1eb5399828c3be8978075b37de6b8a1d995f3e451e9bfcb066fed4aedeefffaac11a94b621328381fae8b23ad39ce9394e02d438981374e838fe5df14ec790c0cb32466dd28977721bd0343398df49c70302891de22d8b3c9b4ba45f697b03e424d985",
      "name": "HD",
      "is_adaptive": "0",
      "watch-timeout": 30
    },
    {
      "id": 2,
      "quality": "low",
      "url": "https://edge-823-ch-gv.filmon.com/live/851.low.stream/playlist.m3u8?id=0ad5aac39bb13fbe9156feccde25a24b9cc4d8e2b6e69fa51da411a8ed21acb05754a1af9e5ac6f49aae0fc0bbf50627bf2d318bc15f46661961ce802c13bddd0d17d7d7f5270ae52da16e0392517fedc2269996507b6d916b98f70059382aeea78be535a03691553fe63f75a00d4732c8cdd4777d724735131582431fd5ae4b50752fe3e672cd7df2007edbdd3bab9838ce25427aaa02c2",
      "name": "SD",
      "is_adaptive": "0",
      "watch-timeout": 30
    }
  ],
  "serverURL": "https://edge-823-ch-gv.filmon.com/live/851.low.stream/playlist.m3u8?id=0ad5aac39bb13fbe9156feccde25a24b9cc4d8e2b6e69fa51da411a8ed21acb05754a1af9e5ac6f49aae0fc0bbf50627bf2d318bc15f46661961ce802c13bddd0d17d7d7f5270ae52da16e0392517fedc2269996507b6d916b98f70059382aeea78be535a03691553fe63f75a00d4732c8cdd4777d724735131582431fd5ae4b50752fe3e672cd7df2007edbdd3bab9838ce25427aaa02c2",
  "streamName": "SD",
  "enable_local_tvguide": true,
  "server_time": 1644002572
}
```


Resolving patch:
diff --git a/src/streamlink/plugins/filmon.py b/src/streamlink/plugins/filmon.py
--- a/src/streamlink/plugins/filmon.py
+++ b/src/streamlink/plugins/filmon.py
@@ -6,8 +6,10 @@
 from streamlink.exceptions import PluginError, StreamError
 from streamlink.plugin import Plugin, pluginmatcher
 from streamlink.plugin.api import validate
+from streamlink.plugin.api.http_session import TLSSecLevel1Adapter
 from streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWorker, Sequence
 from streamlink.stream.hls_playlist import load as load_hls_playlist
+from streamlink.stream.http import HTTPStream
 
 log = logging.getLogger(__name__)
 
@@ -122,24 +124,32 @@ class FilmOnAPI:
     def __init__(self, session):
         self.session = session
 
-    channel_url = "http://www.filmon.com/api-v2/channel/{0}?protocol=hls"
-    vod_url = "http://www.filmon.com/vod/info/{0}"
+    channel_url = "https://www.filmon.com/ajax/getChannelInfo"
+    vod_url = "https://vms-admin.filmon.com/api/video/movie?id={0}"
 
     stream_schema = {
-        "quality": validate.text,
+        "quality": str,
         "url": validate.url(),
         "watch-timeout": int
     }
-    api_schema = validate.Schema(
+    channel_schema = validate.Schema(
         {
-            "data": {
+            "streams": validate.any(
+                {str: stream_schema},
+                [stream_schema]
+            )
+        }
+    )
+    vod_schema = validate.Schema(
+        {
+            "response": {
                 "streams": validate.any(
-                    {validate.text: stream_schema},
+                    {str: stream_schema},
                     [stream_schema]
                 )
             }
         },
-        validate.get("data")
+        validate.get("response")
     )
 
     def channel(self, channel):
@@ -150,11 +160,13 @@ def channel(self, channel):
 
             # retry for 50X errors
             try:
-                res = self.session.http.get(self.channel_url.format(channel))
+                res = self.session.http.post(self.channel_url,
+                                             data={"channel_id": channel, "quality": "low"},
+                                             headers={"X-Requested-With": "XMLHttpRequest"})
                 if res:
                     # retry for invalid response data
                     try:
-                        return self.session.http.json(res, schema=self.api_schema)
+                        return self.session.http.json(res, schema=self.channel_schema)
                     except PluginError:
                         log.debug("invalid or non-JSON data received")
                         continue
@@ -165,7 +177,7 @@ def channel(self, channel):
 
     def vod(self, vod_id):
         res = self.session.http.get(self.vod_url.format(vod_id))
-        return self.session.http.json(res, schema=self.api_schema)
+        return self.session.http.json(res, schema=self.vod_schema)
 
 
 @pluginmatcher(re.compile(r"""
@@ -183,7 +195,7 @@ def vod(self, vod_id):
             (?P<is_group>group/)
         )(?:channel_id=)?(?P<channel>[-_\w]+)
         |
-        vod/view/(?P<vod_id>\d+)-
+        vod/view/(?P<vod_id>[^/?&]+)
     )
 """, re.VERBOSE))
 class Filmon(Plugin):
@@ -220,14 +232,28 @@ def _get_streams(self):
         vod_id = self.match.group("vod_id")
         is_group = self.match.group("is_group")
 
+        adapter = TLSSecLevel1Adapter()
+        self.session.http.mount("https://filmon.com", adapter)
+        self.session.http.mount("https://www.filmon.com", adapter)
+        self.session.http.mount("https://vms-admin.filmon.com/", adapter)
+
+        # get cookies
+        self.session.http.get(self.url)
+
         if vod_id:
             data = self.api.vod(vod_id)
             for _, stream in data["streams"].items():
-                streams = HLSStream.parse_variant_playlist(self.session, stream["url"])
-                if not streams:
-                    yield stream["quality"], HLSStream(self.session, stream["url"])
+                if stream["url"].endswith(".m3u8"):
+                    streams = HLSStream.parse_variant_playlist(self.session, stream["url"])
+                    if not streams:
+                        yield stream["quality"], HLSStream(self.session, stream["url"])
+                    else:
+                        yield from streams.items()
+                elif stream["url"].endswith(".mp4"):
+                    yield stream["quality"], HTTPStream(self.session, stream["url"])
                 else:
-                    yield from streams.items()
+                    log.error("Unsupported stream type")
+                    return
         else:
             if channel and not channel.isdigit():
                 _id = self.cache.get(channel)


Test patch:
diff --git a/tests/plugins/test_filmon.py b/tests/plugins/test_filmon.py
--- a/tests/plugins/test_filmon.py
+++ b/tests/plugins/test_filmon.py
@@ -31,7 +31,10 @@ class TestPluginCanHandleUrlFilmon(PluginCanHandleUrl):
     ('https://www.filmon.com/tv/channel-4', [None, 'channel-4', None]),
     ('https://www.filmon.tv/tv/55', [None, '55', None]),
     ('http://www.filmon.tv/group/comedy', ['group/', 'comedy', None]),
-    ('http://www.filmon.tv/vod/view/10250-0-crime-boss', [None, None, '10250'])
+    ('http://www.filmon.tv/vod/view/10250-0-crime-boss', [None, None, '10250-0-crime-boss']),
+    ('http://www.filmon.tv/vod/view/10250-0-crime-boss/extra', [None, None, '10250-0-crime-boss']),
+    ('http://www.filmon.tv/vod/view/10250-0-crime-boss?extra', [None, None, '10250-0-crime-boss']),
+    ('http://www.filmon.tv/vod/view/10250-0-crime-boss&extra', [None, None, '10250-0-crime-boss'])
 ])
 def test_match_url(url, expected):
     Filmon.bind(Mock(), "tests.plugins.test_filmon")