Repository: yt-dlp/yt-dlp
Pull request #5082 (2022-09-30 13:03:34)

Extractor for Microsoft embedded videos
### Description of your *pull request* and other information

</details>

<!--

Explanation of your *pull request* in arbitrary form goes here. Please **make sure the description explains the purpose and effect** of your *pull request* and is worded well enough to be understood. Provide as much **context and examples** as possible

-->

An Extractor for Microsoft.com embedded videos. 

This feature proposal was created in support of the yt-dlp project during Hacktoberfest. It allows a user to scrape videos, metadata, thumbnails and subtitles. More information about this extractor can be found in issue #2638.

All lint errors have been resolved using `flake8` and a test has succeeded using CPython 3.10. The test used the `__main__.py` file as the entrypoint of a command line invocation.

This is a typical printout from a linux desktop's test:

    sushimint@sushimint:~/Desktop/Workshop/yt-dlp-fork$ cd yt_dlp
	sushimint@sushimint:~/Desktop/Workshop/yt-dlp-fork/yt_dlp$ python3 __main__.py https://www.microsoft.com/en-us/videoplayer/embed/RWL07e -v --write-subs
	[debug] Command-line config: ['https://www.microsoft.com/en-us/videoplayer/embed/RWL07e', '-v', '--write-subs']
	[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
	[debug] yt-dlp version 2022.09.01 [5d7c7d656] (source)
	[debug] Lazy loading extractors is disabled
	[debug] Plugins: ['SamplePluginIE', 'SamplePluginPP']
	[debug] Git HEAD: a7cdda41f
	[debug] Python 3.8.10 (CPython 64bit) - Linux-5.4.0-126-generic-x86_64-with-glibc2.29 (glibc 2.31)
	[debug] Checking exe version: ffmpeg -bsfs
	[debug] Checking exe version: ffprobe -bsfs
	[debug] exe versions: ffmpeg 4.2.7, ffprobe 4.2.7, rtmpdump 2.4
	[debug] Optional libraries: Cryptodome-3.6.1, brotli-1.0.7, certifi-2019.11.28, mutagen-1.44.0, pyxattr-0.6.1, secretstorage-2.3.1, sqlite3-2.6.0, websockets-8.1
	[debug] Proxy map: {}
	[debug] Loaded 1678 extractors
	[debug] [MicrosoftEmbed] Extracting URL: https://www.microsoft.com/en-us/videoplayer/embed/RWL07e
	[MicrosoftEmbed] RWL07e: Downloading JSON metadata
	[MicrosoftEmbed] RWL07e: Downloading m3u8 information
	[MicrosoftEmbed] RWL07e: Downloading MPD manifest
	[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, filesize, fs_approx, tbr, vbr, abr, asr, proto, vext, aext, hasaud, source, id
	[info] RWL07e: Downloading subtitles: en-us
	[debug] Default format spec: bestvideo*+bestaudio/best
	[info] RWL07e: Downloading 1 format(s): 6722+106
	[info] Writing video subtitles to: Microsoft for Public Health and Social Services [RWL07e].en-us.vtt
	[debug] Invoking http downloader on "https://prod-video-cms-rt-microsoft-com.akamaized.net/cms/api/am/videofiledata/RWL07e-enus?ver=032b"
	[download] Destination: Microsoft for Public Health and Social Services [RWL07e].en-us.vtt
	[download] 100% of 5.03KiB in    00:00 at 231.32KiB/s
	[debug] Invoking hlsnative downloader on "https://wus-streaming-video-rt-microsoft-com.akamaized.net/064e1812-e02f-4fea-aff8-af73e11b3661/b6d9ea70-00f6-4786-be08-4fc9d1bf.ism/QualityLevels(6465762)/Manifest(video,format=m3u8-aapl)"
	[hlsnative] Downloading m3u8 manifest
	[hlsnative] Total fragments: 10
	[download] Destination: Microsoft for Public Health and Social Services [RWL07e].f6722.mp4
	[download] 100% of 79.19MiB in    00:02 at 29.51MiB/s
	[debug] Invoking hlsnative downloader on "https://wus-streaming-video-rt-microsoft-com.akamaized.net/064e1812-e02f-4fea-aff8-af73e11b3661/b6d9ea70-00f6-4786-be08-4fc9d1bf.ism/QualityLevels(96020)/Manifest(aac_UND_2_97,format=m3u8-aapl)"
	[hlsnative] Downloading m3u8 manifest
	[hlsnative] Total fragments: 10
	[download] Destination: Microsoft for Public Health and Social Services [RWL07e].f106.m4a
	[download] 100% of 1.24MiB in    00:00 at 3.72MiB/s
	[debug] ffmpeg command line: ffprobe -show_streams 'file:Microsoft for Public Health and Social Services [RWL07e].f106.m4a'
	[Merger] Merging formats into "Microsoft for Public Health and Social Services [RWL07e].mp4"
	[debug] ffmpeg command line: ffmpeg -y -loglevel repeat+info -i 'file:Microsoft for Public Health and Social Services [RWL07e].f6722.mp4' -i 'file:Microsoft for Public Health and Social Services [RWL07e].f106.m4a' -c copy -map 0:v:0 -map 1:a:0 -bsf:a:0 aac_adtstoasc -movflags +faststart 'file:Microsoft for Public Health and Social Services [RWL07e].temp.mp4'
	Deleting original file Microsoft for Public Health and Social Services [RWL07e].f6722.mp4 (pass -k to keep)
	Deleting original file Microsoft for Public Health and Social Services [RWL07e].f106.m4a (pass -k to keep)
	sushimint@sushimint:~/Desktop/Workshop/yt-dlp-fork/yt_dlp$  

The test took the following URL as input: [link](https://www.microsoft.com/en-us/videoplayer/embed/RWL07e)
The test resulted in two files being generated: 
    `Microsoft for Public Health and Social Services [RWL07e].mp4` 
    `Microsoft for Public Health and Social Services [RWL07e].en-us.vtt`

<details open><summary>Template</summary> <!-- OPEN is intentional -->

<!--

# PLEASE FOLLOW THE GUIDE BELOW

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
- Use *Preview* tab to see how your *pull request* will actually look like

-->

### Before submitting a *pull request* make sure you have:
- [X] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [X] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
- [X] Checked the code with [flake8](https://pypi.python.org/pypi/flake8) and [ran relevant tests](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions)

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check one of the following options:
- [X] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
- [ ] I am not the original author of this code but it is in public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)

### What is the purpose of your *pull request*?
- [ ] Fix or improvement to an extractor (Make sure to add/update tests)
- [X] New extractor ([Piracy websites will not be accepted](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-website-primarily-used-for-piracy))
- [ ] Core bug fix/improvement
- [ ] New feature (It is strongly [recommended to open an issue first](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#adding-new-feature-or-making-overarching-changes))


pukkandan: > and with a simple test. The test used the __main__.py file as the entrypoint of a command line invocation.

We have a test runner. See point 6 in https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#adding-support-for-a-new-site

Patch:
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index 4fcf1f5cc..bc6de4926 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -3640,7 +3640,7 @@ def render_thumbnails_table(self, info_dict):
             return None
         return render_table(
             self._list_format_headers('ID', 'Width', 'Height', 'URL'),
-            [[t.get('id'), t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails])
+            [[t.get('id'), t.get('width') or 'unknown', t.get('height') or 'unknown', t['url']] for t in thumbnails])
 
     def render_subtitles_table(self, video_id, subtitles):
         def _row(lang, formats):
diff --git a/yt_dlp/downloader/external.py b/yt_dlp/downloader/external.py
index d117c06e0..895390d6c 100644
--- a/yt_dlp/downloader/external.py
+++ b/yt_dlp/downloader/external.py
@@ -252,6 +252,10 @@ def supports_manifest(manifest):
         check_results = (not re.search(feature, manifest) for feature in UNSUPPORTED_FEATURES)
         return all(check_results)
 
+    @staticmethod
+    def _aria2c_filename(fn):
+        return fn if os.path.isabs(fn) else f'.{os.path.sep}{fn}'
+
     def _make_cmd(self, tmpfilename, info_dict):
         cmd = [self.exe, '-c',
                '--console-log-level=warn', '--summary-interval=0', '--download-result=hide',
@@ -280,11 +284,9 @@ def _make_cmd(self, tmpfilename, info_dict):
         # https://github.com/aria2/aria2/issues/1373
         dn = os.path.dirname(tmpfilename)
         if dn:
-            if not os.path.isabs(dn):
-                dn = f'.{os.path.sep}{dn}'
-            cmd += ['--dir', dn + os.path.sep]
+            cmd += ['--dir', self._aria2c_filename(dn) + os.path.sep]
         if 'fragments' not in info_dict:
-            cmd += ['--out', f'.{os.path.sep}{os.path.basename(tmpfilename)}']
+            cmd += ['--out', self._aria2c_filename(os.path.basename(tmpfilename))]
         cmd += ['--auto-file-renaming=false']
 
         if 'fragments' in info_dict:
@@ -293,11 +295,11 @@ def _make_cmd(self, tmpfilename, info_dict):
             url_list = []
             for frag_index, fragment in enumerate(info_dict['fragments']):
                 fragment_filename = '%s-Frag%d' % (os.path.basename(tmpfilename), frag_index)
-                url_list.append('%s\n\tout=%s' % (fragment['url'], fragment_filename))
+                url_list.append('%s\n\tout=%s' % (fragment['url'], self._aria2c_filename(fragment_filename)))
             stream, _ = self.sanitize_open(url_list_file, 'wb')
             stream.write('\n'.join(url_list).encode())
             stream.close()
-            cmd += ['-i', url_list_file]
+            cmd += ['-i', self._aria2c_filename(url_list_file)]
         else:
             cmd += ['--', info_dict['url']]
         return cmd
diff --git a/yt_dlp/downloader/ism.py b/yt_dlp/downloader/ism.py
index 801b5af81..c961dc62e 100644
--- a/yt_dlp/downloader/ism.py
+++ b/yt_dlp/downloader/ism.py
@@ -138,6 +138,8 @@ def write_piff_header(stream, params):
 
         if fourcc == 'AACL':
             sample_entry_box = box(b'mp4a', sample_entry_payload)
+        if fourcc == 'EC-3':
+            sample_entry_box = box(b'ec-3', sample_entry_payload)
     elif stream_type == 'video':
         sample_entry_payload += u16.pack(0)  # pre defined
         sample_entry_payload += u16.pack(0)  # reserved
diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py
index fa33866df..3a92c1d02 100644
--- a/yt_dlp/extractor/_extractors.py
+++ b/yt_dlp/extractor/_extractors.py
@@ -233,6 +233,7 @@
     BrightcoveNewIE,
 )
 from .businessinsider import BusinessInsiderIE
+from .bundesliga import BundesligaIE
 from .buzzfeed import BuzzFeedIE
 from .byutv import BYUtvIE
 from .c56 import C56IE
@@ -718,6 +719,7 @@
     IHeartRadioIE,
     IHeartRadioPodcastIE,
 )
+from .iltalehti import IltalehtiIE
 from .imdb import (
     ImdbIE,
     ImdbListIE
@@ -958,6 +960,7 @@
     MicrosoftVirtualAcademyIE,
     MicrosoftVirtualAcademyCourseIE,
 )
+from .microsoftembed import MicrosoftEmbedIE
 from .mildom import (
     MildomIE,
     MildomVodIE,
@@ -1079,6 +1082,7 @@
     NBCSportsIE,
     NBCSportsStreamIE,
     NBCSportsVPlayerIE,
+    NBCStationsIE,
 )
 from .ndr import (
     NDRIE,
@@ -1765,6 +1769,8 @@
 from .telewebion import TelewebionIE
 from .tempo import TempoIE
 from .tencent import (
+    IflixEpisodeIE,
+    IflixSeriesIE,
     VQQSeriesIE,
     VQQVideoIE,
     WeTvEpisodeIE,
@@ -1968,6 +1974,7 @@
 from .umg import UMGDeIE
 from .unistra import UnistraIE
 from .unity import UnityIE
+from .unscripted import UnscriptedNewsVideoIE
 from .uol import UOLIE
 from .uplynk import (
     UplynkIE,
diff --git a/yt_dlp/extractor/audioboom.py b/yt_dlp/extractor/audioboom.py
index dc19a3874..f1aa0201b 100644
--- a/yt_dlp/extractor/audioboom.py
+++ b/yt_dlp/extractor/audioboom.py
@@ -2,6 +2,8 @@
 from ..utils import (
     clean_html,
     float_or_none,
+    unescapeHTML,
+    traverse_obj,
 )
 
 
@@ -9,16 +11,28 @@ class AudioBoomIE(InfoExtractor):
     _VALID_URL = r'https?://(?:www\.)?audioboom\.com/(?:boos|posts)/(?P<id>[0-9]+)'
     _TESTS = [{
         'url': 'https://audioboom.com/posts/7398103-asim-chaudhry',
-        'md5': '7b00192e593ff227e6a315486979a42d',
+        'md5': '4d68be11c9f9daf3dab0778ad1e010c3',
         'info_dict': {
             'id': '7398103',
             'ext': 'mp3',
             'title': 'Asim Chaudhry',
-            'description': 'md5:2f3fef17dacc2595b5362e1d7d3602fc',
+            'description': 'md5:0ed714ae0e81e5d9119cac2f618ad679',
             'duration': 4000.99,
             'uploader': 'Sue Perkins: An hour or so with...',
             'uploader_url': r're:https?://(?:www\.)?audioboom\.com/channel/perkins',
         }
+    }, {  # Direct mp3-file link
+        'url': 'https://audioboom.com/posts/8128496.mp3',
+        'md5': 'e329edf304d450def95c7f86a9165ee1',
+        'info_dict': {
+            'id': '8128496',
+            'ext': 'mp3',
+            'title': 'TCRNo8 / DAILY 03 - In Control',
+            'description': 'md5:44665f142db74858dfa21c5b34787948',
+            'duration': 1689.7,
+            'uploader': 'Lost Dot Podcast: The Trans Pyrenees and Transcontinental Race',
+            'uploader_url': r're:https?://(?:www\.)?audioboom\.com/channels/5003904',
+        }
     }, {
         'url': 'https://audioboom.com/posts/4279833-3-09-2016-czaban-hour-3?t=0',
         'only_matching': True,
@@ -26,45 +40,23 @@ class AudioBoomIE(InfoExtractor):
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
+        webpage = self._download_webpage(f'https://audioboom.com/posts/{video_id}', video_id)
 
-        webpage = self._download_webpage(url, video_id)
-
-        clip = None
-
-        clip_store = self._parse_json(
-            self._html_search_regex(
-                r'data-new-clip-store=(["\'])(?P<json>{.+?})\1',
-                webpage, 'clip store', default='{}', group='json'),
-            video_id, fatal=False)
-        if clip_store:
-            clips = clip_store.get('clips')
-            if clips and isinstance(clips, list) and isinstance(clips[0], dict):
-                clip = clips[0]
-
-        def from_clip(field):
-            if clip:
-                return clip.get(field)
-
-        audio_url = from_clip('clipURLPriorToLoading') or self._og_search_property(
-            'audio', webpage, 'audio url')
-        title = from_clip('title') or self._html_search_meta(
-            ['og:title', 'og:audio:title', 'audio_title'], webpage)
-        description = from_clip('description') or clean_html(from_clip('formattedDescription')) or self._og_search_description(webpage)
-
-        duration = float_or_none(from_clip('duration') or self._html_search_meta(
-            'weibo:audio:duration', webpage))
-
-        uploader = from_clip('author') or self._html_search_meta(
-            ['og:audio:artist', 'twitter:audio:artist_name', 'audio_artist'], webpage, 'uploader')
-        uploader_url = from_clip('author_url') or self._html_search_meta(
-            'audioboo:channel', webpage, 'uploader url')
+        clip_store = self._search_json(
+            r'data-react-class="V5DetailPagePlayer"\s*data-react-props=["\']',
+            webpage, 'clip store', video_id, fatal=False, transform_source=unescapeHTML)
+        clip = traverse_obj(clip_store, ('clips', 0), expected_type=dict) or {}
 
         return {
             'id': video_id,
-            'url': audio_url,
-            'title': title,
-            'description': description,
-            'duration': duration,
-            'uploader': uploader,
-            'uploader_url': uploader_url,
+            'url': clip.get('clipURLPriorToLoading') or self._og_search_property('audio', webpage, 'audio url'),
+            'title': clip.get('title') or self._html_search_meta(['og:title', 'og:audio:title', 'audio_title'], webpage),
+            'description': (clip.get('description') or clean_html(clip.get('formattedDescription'))
+                            or self._og_search_description(webpage)),
+            'duration': float_or_none(clip.get('duration') or self._html_search_meta('weibo:audio:duration', webpage)),
+            'uploader': clip.get('author') or self._html_search_meta(
+                ['og:audio:artist', 'twitter:audio:artist_name', 'audio_artist'], webpage, 'uploader'),
+            'uploader_url': clip.get('author_url') or self._html_search_regex(
+                r'<div class="avatar flex-shrink-0">\s*<a href="(?P<uploader_url>http[^"]+)"',
+                webpage, 'uploader url', fatal=False),
         }
diff --git a/yt_dlp/extractor/bongacams.py b/yt_dlp/extractor/bongacams.py
index cbef0fc53..9ba166b04 100644
--- a/yt_dlp/extractor/bongacams.py
+++ b/yt_dlp/extractor/bongacams.py
@@ -8,13 +8,28 @@
 
 
 class BongaCamsIE(InfoExtractor):
-    _VALID_URL = r'https?://(?P<host>(?:[^/]+\.)?bongacams\d*\.com)/(?P<id>[^/?&#]+)'
+    _VALID_URL = r'https?://(?P<host>(?:[^/]+\.)?bongacams\d*\.(?:com|net))/(?P<id>[^/?&#]+)'
     _TESTS = [{
         'url': 'https://de.bongacams.com/azumi-8',
         'only_matching': True,
     }, {
         'url': 'https://cn.bongacams.com/azumi-8',
         'only_matching': True,
+    }, {
+        'url': 'https://de.bongacams.net/claireashton',
+        'info_dict': {
+            'id': 'claireashton',
+            'ext': 'mp4',
+            'title': r're:ClaireAshton \d{4}-\d{2}-\d{2} \d{2}:\d{2}',
+            'age_limit': 18,
+            'uploader_id': 'ClaireAshton',
+            'uploader': 'ClaireAshton',
+            'like_count': int,
+            'is_live': True,
+        },
+        'params': {
+            'skip_download': True,
+        },
     }]
 
     def _real_extract(self, url):
diff --git a/yt_dlp/extractor/bundesliga.py b/yt_dlp/extractor/bundesliga.py
new file mode 100644
index 000000000..e76dd58dd
--- /dev/null
+++ b/yt_dlp/extractor/bundesliga.py
@@ -0,0 +1,34 @@
+from .common import InfoExtractor
+from .jwplatform import JWPlatformIE
+
+
+class BundesligaIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?bundesliga\.com/[a-z]{2}/bundesliga/videos(?:/[^?]+)?\?vid=(?P<id>[a-zA-Z0-9]{8})'
+    _TESTS = [
+        {
+            'url': 'https://www.bundesliga.com/en/bundesliga/videos?vid=bhhHkKyN',
+            'md5': '8fc3b25cd12440e3a8cdc51f1493849c',
+            'info_dict': {
+                'id': 'bhhHkKyN',
+                'ext': 'mp4',
+                'title': 'Watch: Alphonso Davies and Jeremie Frimpong head-to-head',
+                'thumbnail': 'https://cdn.jwplayer.com/v2/media/bhhHkKyN/poster.jpg?width=720',
+                'upload_date': '20220928',
+                'duration': 146,
+                'timestamp': 1664366511,
+                'description': 'md5:803d4411bd134140c774021dd4b7598b'
+            }
+        },
+        {
+            'url': 'https://www.bundesliga.com/en/bundesliga/videos/latest-features/T8IKc8TX?vid=ROHjs06G',
+            'only_matching': True
+        },
+        {
+            'url': 'https://www.bundesliga.com/en/bundesliga/videos/goals?vid=mOG56vWA',
+            'only_matching': True
+        }
+    ]
+
+    def _real_extract(self, url):
+        video_id = self._match_id(url)
+        return self.url_result(f'jwplatform:{video_id}', JWPlatformIE, video_id)
diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py
index d36f025ab..caec0ccf6 100644
--- a/yt_dlp/extractor/common.py
+++ b/yt_dlp/extractor/common.py
@@ -1227,7 +1227,7 @@ def _search_regex(self, pattern, string, name, default=NO_DEFAULT, fatal=True, f
             return None
 
     def _search_json(self, start_pattern, string, name, video_id, *, end_pattern='',
-                     contains_pattern='(?s:.+)', fatal=True, default=NO_DEFAULT, **kwargs):
+                     contains_pattern=r'{(?s:.+)}', fatal=True, default=NO_DEFAULT, **kwargs):
         """Searches string for the JSON object specified by start_pattern"""
         # NB: end_pattern is only used to reduce the size of the initial match
         if default is NO_DEFAULT:
@@ -1236,7 +1236,7 @@ def _search_json(self, start_pattern, string, name, video_id, *, end_pattern='',
             fatal, has_default = False, True
 
         json_string = self._search_regex(
-            rf'(?:{start_pattern})\s*(?P<json>{{\s*(?:{contains_pattern})\s*}})\s*(?:{end_pattern})',
+            rf'(?:{start_pattern})\s*(?P<json>{contains_pattern})\s*(?:{end_pattern})',
             string, name, group='json', fatal=fatal, default=None if has_default else NO_DEFAULT)
         if not json_string:
             return default
@@ -3124,9 +3124,10 @@ def _parse_ism_formats_and_subtitles(self, ism_doc, ism_url, ism_id=None):
             stream_name = stream.get('Name')
             stream_language = stream.get('Language', 'und')
             for track in stream.findall('QualityLevel'):
-                fourcc = track.get('FourCC') or ('AACL' if track.get('AudioTag') == '255' else None)
+                KNOWN_TAGS = {'255': 'AACL', '65534': 'EC-3'}
+                fourcc = track.get('FourCC') or KNOWN_TAGS.get(track.get('AudioTag'))
                 # TODO: add support for WVC1 and WMAP
-                if fourcc not in ('H264', 'AVC1', 'AACL', 'TTML'):
+                if fourcc not in ('H264', 'AVC1', 'AACL', 'TTML', 'EC-3'):
                     self.report_warning('%s is not a supported codec' % fourcc)
                     continue
                 tbr = int(track.attrib['Bitrate']) // 1000
diff --git a/yt_dlp/extractor/dropbox.py b/yt_dlp/extractor/dropbox.py
index 0d12513b2..54d97a25d 100644
--- a/yt_dlp/extractor/dropbox.py
+++ b/yt_dlp/extractor/dropbox.py
@@ -54,7 +54,7 @@ def _real_extract(self, url):
                 raise ExtractorError('Password protected video, use --video-password <password>', expected=True)
 
         info_json = self._search_json(r'InitReact\.mountComponent\(.*?,', webpage, 'mountComponent', video_id,
-                                      contains_pattern=r'.+?"preview".+?', end_pattern=r'\)')['props']
+                                      contains_pattern=r'{.+?"preview".+?}', end_pattern=r'\)')['props']
         transcode_url = traverse_obj(info_json, ((None, 'preview'), 'file', 'preview', 'content', 'transcode_url'), get_all=False)
         formats, subtitles = self._extract_m3u8_formats_and_subtitles(transcode_url, video_id)
 
diff --git a/yt_dlp/extractor/hrfensehen.py b/yt_dlp/extractor/hrfensehen.py
index 6f7ed9b4b..dd72d86d7 100644
--- a/yt_dlp/extractor/hrfensehen.py
+++ b/yt_dlp/extractor/hrfensehen.py
@@ -1,14 +1,19 @@
 import json
 import re
 
-from ..utils import int_or_none, unified_timestamp, unescapeHTML
+from ..utils import (
+    int_or_none,
+    traverse_obj,
+    try_call,
+    unescapeHTML,
+    unified_timestamp,
+)
 from .common import InfoExtractor
 
 
 class HRFernsehenIE(InfoExtractor):
     IE_NAME = 'hrfernsehen'
     _VALID_URL = r'^https?://www\.(?:hr-fernsehen|hessenschau)\.de/.*,video-(?P<id>[0-9]{6})\.html'
-
     _TESTS = [{
         'url': 'https://www.hessenschau.de/tv-sendung/hessenschau-vom-26082020,video-130546.html',
         'md5': '5c4e0ba94677c516a2f65a84110fc536',
@@ -21,10 +26,11 @@ class HRFernsehenIE(InfoExtractor):
             'subtitles': {'de': [{
                 'url': 'https://hr-a.akamaihd.net/video/as/hessenschau/2020_08/hrLogo_200826200407_L385592_512x288-25p-500kbit.vtt'
             }]},
-            'timestamp': 1598470200,
+            'timestamp': 1598400000,
             'upload_date': '20200826',
-            'thumbnail': 'https://www.hessenschau.de/tv-sendung/hs_ganz-1554~_t-1598465545029_v-16to9__medium.jpg',
-            'title': 'hessenschau vom 26.08.2020'
+            'thumbnail': 'https://www.hessenschau.de/tv-sendung/hs_ganz-1554~_t-1598465545029_v-16to9.jpg',
+            'title': 'hessenschau vom 26.08.2020',
+            'duration': 1654
         }
     }, {
         'url': 'https://www.hr-fernsehen.de/sendungen-a-z/mex/sendungen/fair-und-gut---was-hinter-aldis-eigenem-guetesiegel-steckt,video-130544.html',
@@ -33,25 +39,18 @@ class HRFernsehenIE(InfoExtractor):
 
     _GEO_COUNTRIES = ['DE']
 
-    def extract_airdate(self, loader_data):
-        airdate_str = loader_data.get('mediaMetadata', {}).get('agf', {}).get('airdate')
-
-        if airdate_str is None:
-            return None
-
-        return unified_timestamp(airdate_str)
-
     def extract_formats(self, loader_data):
         stream_formats = []
-        for stream_obj in loader_data["videoResolutionLevels"]:
+        data = loader_data['mediaCollection']['streams'][0]['media']
+        for inner in data[1:]:
             stream_format = {
-                'format_id': str(stream_obj['verticalResolution']) + "p",
-                'height': stream_obj['verticalResolution'],
-                'url': stream_obj['url'],
+                'format_id': try_call(lambda: f'{inner["maxHResolutionPx"]}p'),
+                'height': inner.get('maxHResolutionPx'),
+                'url': inner['url'],
             }
 
             quality_information = re.search(r'([0-9]{3,4})x([0-9]{3,4})-([0-9]{2})p-([0-9]{3,4})kbit',
-                                            stream_obj['url'])
+                                            inner['url'])
             if quality_information:
                 stream_format['width'] = int_or_none(quality_information.group(1))
                 stream_format['height'] = int_or_none(quality_information.group(2))
@@ -72,22 +71,22 @@ def _real_extract(self, url):
         description = self._html_search_meta(
             ['description'], webpage)
 
-        loader_str = unescapeHTML(self._search_regex(r"data-new-hr-mediaplayer-loader='([^']*)'", webpage, "ardloader"))
+        loader_str = unescapeHTML(self._search_regex(r"data-(?:new-)?hr-mediaplayer-loader='([^']*)'", webpage, 'ardloader'))
         loader_data = json.loads(loader_str)
 
+        subtitle = traverse_obj(loader_data, ('mediaCollection', 'subTitles', 0, 'sources', 0, 'url'))
+
         info = {
             'id': video_id,
             'title': title,
             'description': description,
             'formats': self.extract_formats(loader_data),
-            'timestamp': self.extract_airdate(loader_data)
+            'subtitles': {'de': [{'url': subtitle}]},
+            'timestamp': unified_timestamp(self._search_regex(
+                r'<time\sdatetime="(\d{4}\W\d{1,2}\W\d{1,2})', webpage, 'datetime', fatal=False)),
+            'duration': int_or_none(traverse_obj(
+                loader_data, ('playerConfig', 'pluginData', 'trackingAti@all', 'richMedia', 'duration'))),
+            'thumbnail': self._search_regex(r'thumbnailUrl\W*([^"]+)', webpage, 'thumbnail', default=None),
         }
 
-        if "subtitle" in loader_data:
-            info["subtitles"] = {"de": [{"url": loader_data["subtitle"]}]}
-
-        thumbnails = list(set([t for t in loader_data.get("previewImageUrl", {}).values()]))
-        if len(thumbnails) > 0:
-            info["thumbnails"] = [{"url": t} for t in thumbnails]
-
         return info
diff --git a/yt_dlp/extractor/iltalehti.py b/yt_dlp/extractor/iltalehti.py
new file mode 100644
index 000000000..a40307aed
--- /dev/null
+++ b/yt_dlp/extractor/iltalehti.py
@@ -0,0 +1,51 @@
+from .common import InfoExtractor
+from ..utils import js_to_json, traverse_obj
+
+
+class IltalehtiIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?iltalehti\.fi/[^/?#]+/a/(?P<id>[^/?#])'
+    _TESTS = [
+        # jwplatform embed main_media
+        {
+            'url': 'https://www.iltalehti.fi/ulkomaat/a/9fbd067f-94e4-46cd-8748-9d958eb4dae2',
+            'md5': 'af12d42c539f1f49f0b62d231fe72dcd',
+            'info_dict': {
+                'id': 'gYjjaf1L',
+                'ext': 'mp4',
+                'title': 'Sensuroimaton Päivärinta, jakso 227: Vieraana Suomen Venäjän ex-suurlähettiläs René Nyberg ja Kenraalimajuri evp Pekka Toveri',
+                'description': '',
+                'upload_date': '20220928',
+                'timestamp': 1664360878,
+                'duration': 2089,
+                'thumbnail': r're:^https?://.*\.jpg',
+            },
+        },
+        # jwplatform embed body
+        {
+            'url': 'https://www.iltalehti.fi/politiikka/a/1ce49d85-1670-428b-8db8-d2479b9950a4',
+            'md5': '9e50334b8f8330ce8828b567a82a3c65',
+            'info_dict': {
+                'id': '18R6zkLi',
+                'ext': 'mp4',
+                'title': 'Pekka Toverin arvio: Näin Nord Stream -kaasuputken räjäyttäminen on saatettu toteuttaa',
+                'description': 'md5:3d1302c9e17e7ffd564143ff58f8de35',
+                'upload_date': '20220929',
+                'timestamp': 1664435867,
+                'duration': 165.0,
+                'thumbnail': r're:^https?://.*\.jpg',
+            },
+        },
+    ]
+
+    def _real_extract(self, url):
+        article_id = self._match_id(url)
+        webpage = self._download_webpage(url, article_id)
+        info = self._search_json(
+            r'<script>\s*window.App\s*=\s*', webpage, 'json', article_id,
+            transform_source=js_to_json)
+        props = traverse_obj(info, (
+            'state', 'articles', ..., 'items', (('main_media', 'properties'), ('body', ..., 'properties'))))
+        video_ids = traverse_obj(props, (lambda _, v: v['provider'] == 'jwplayer', 'id'))
+        return self.playlist_from_matches(
+            video_ids, article_id, ie='JWPlatform', getter=lambda id: f'jwplatform:{id}',
+            title=traverse_obj(info, ('state', 'articles', ..., 'items', 'canonical_title'), get_all=False))
diff --git a/yt_dlp/extractor/malltv.py b/yt_dlp/extractor/malltv.py
index bfd6008b3..02f226be5 100644
--- a/yt_dlp/extractor/malltv.py
+++ b/yt_dlp/extractor/malltv.py
@@ -14,7 +14,7 @@ class MallTVIE(InfoExtractor):
     _VALID_URL = r'https?://(?:(?:www|sk)\.)?mall\.tv/(?:[^/]+/)*(?P<id>[^/?#&]+)'
     _TESTS = [{
         'url': 'https://www.mall.tv/18-miliard-pro-neziskovky-opravdu-jsou-sportovci-nebo-clovek-v-tisni-pijavice',
-        'md5': '1c4a37f080e1f3023103a7b43458e518',
+        'md5': 'cd69ce29176f6533b65bff69ed9a5f2a',
         'info_dict': {
             'id': 't0zzt0',
             'display_id': '18-miliard-pro-neziskovky-opravdu-jsou-sportovci-nebo-clovek-v-tisni-pijavice',
@@ -25,6 +25,11 @@ class MallTVIE(InfoExtractor):
             'timestamp': 1538870400,
             'upload_date': '20181007',
             'view_count': int,
+            'comment_count': int,
+            'thumbnail': 'https://cdn.vpplayer.tech/agmipnzv/encode/vjsnigfq/thumbnails/retina.jpg',
+            'average_rating': 9.060869565217391,
+            'dislike_count': int,
+            'like_count': int,
         }
     }, {
         'url': 'https://www.mall.tv/kdo-to-plati/18-miliard-pro-neziskovky-opravdu-jsou-sportovci-nebo-clovek-v-tisni-pijavice',
@@ -32,6 +37,24 @@ class MallTVIE(InfoExtractor):
     }, {
         'url': 'https://sk.mall.tv/gejmhaus/reklamacia-nehreje-vyrobnik-tepla-alebo-spekacka',
         'only_matching': True,
+    }, {
+        'url': 'https://www.mall.tv/zivoty-slavnych/nadeje-vychodu-i-zapadu-jak-michail-gorbacov-zmenil-politickou-mapu-sveta-a-ziskal-za-to-nobelovu-cenu-miru',
+        'info_dict': {
+            'id': 'yx010y',
+            'ext': 'mp4',
+            'dislike_count': int,
+            'description': 'md5:aee02bee5a8d072c6a8207b91d1905a9',
+            'thumbnail': 'https://cdn.vpplayer.tech/agmipnzv/encode/vjsnjdeu/thumbnails/retina.jpg',
+            'comment_count': int,
+            'display_id': 'md5:0ec2afa94d2e2b7091c019cef2a43a9b',
+            'like_count': int,
+            'duration': 752,
+            'timestamp': 1646956800,
+            'title': 'md5:fe79385daaf16d74c12c1ec4a26687af',
+            'view_count': int,
+            'upload_date': '20220311',
+            'average_rating': 9.685714285714285,
+        }
     }]
 
     def _real_extract(self, url):
@@ -43,12 +66,12 @@ def _real_extract(self, url):
         video = self._parse_json(self._search_regex(
             r'videoObject\s*=\s*JSON\.parse\(JSON\.stringify\(({.+?})\)\);',
             webpage, 'video object'), display_id)
-        video_source = video['VideoSource']
+
         video_id = self._search_regex(
-            r'/([\da-z]+)/index\b', video_source, 'video id')
+            r'<input\s*id\s*=\s*player-id-name\s*[^>]+value\s*=\s*(\w+)', webpage, 'video id')
 
         formats = self._extract_m3u8_formats(
-            video_source + '.m3u8', video_id, 'mp4', 'm3u8_native')
+            video['VideoSource'], video_id, 'mp4', 'm3u8_native')
         self._sort_formats(formats)
 
         subtitles = {}
@@ -69,7 +92,7 @@ def get_count(k):
         info = self._search_json_ld(webpage, video_id, default={})
 
         return merge_dicts({
-            'id': video_id,
+            'id': str(video_id),
             'display_id': display_id,
             'title': video.get('Title'),
             'description': clean_html(video.get('Description')),
diff --git a/yt_dlp/extractor/microsoftembed.py b/yt_dlp/extractor/microsoftembed.py
new file mode 100644
index 000000000..8cdf66778
--- /dev/null
+++ b/yt_dlp/extractor/microsoftembed.py
@@ -0,0 +1,70 @@
+from .common import InfoExtractor
+from ..utils import (
+    int_or_none,
+    traverse_obj,
+    unified_timestamp,
+)
+
+
+class MicrosoftEmbedIE(InfoExtractor):
+    _VALID_URL = r'https?://(?:www\.)?microsoft\.com/(?:[^/]+/)?videoplayer/embed/(?P<id>[a-z0-9A-Z]+)'
+
+    _TESTS = [{
+        'url': 'https://www.microsoft.com/en-us/videoplayer/embed/RWL07e',
+        'md5': 'eb0ae9007f9b305f9acd0a03e74cb1a9',
+        'info_dict': {
+            'id': 'RWL07e',
+            'title': 'Microsoft for Public Health and Social Services',
+            'ext': 'mp4',
+            'thumbnail': 'http://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RWL7Ju?ver=cae5',
+            'age_limit': 0,
+            'timestamp': 1631658316,
+            'upload_date': '20210914'
+        }
+    }]
+    _API_URL = 'https://prod-video-cms-rt-microsoft-com.akamaized.net/vhs/api/videos/'
+
+    def _real_extract(self, url):
+        video_id = self._match_id(url)
+        metadata = self._download_json(self._API_URL + video_id, video_id)
+
+        formats = []
+        for source_type, source in metadata['streams'].items():
+            if source_type == 'smooth_Streaming':
+                formats.extend(self._extract_ism_formats(source['url'], video_id, 'mss'))
+            elif source_type == 'apple_HTTP_Live_Streaming':
+                formats.extend(self._extract_m3u8_formats(source['url'], video_id, 'mp4'))
+            elif source_type == 'mPEG_DASH':
+                formats.extend(self._extract_mpd_formats(source['url'], video_id))
+            else:
+                formats.append({
+                    'format_id': source_type,
+                    'url': source['url'],
+                    'height': source.get('heightPixels'),
+                    'width': source.get('widthPixels'),
+                })
+        self._sort_formats(formats)
+
+        subtitles = {
+            lang: [{
+                'url': data.get('url'),
+                'ext': 'vtt',
+            }] for lang, data in traverse_obj(metadata, 'captions', default={}).items()
+        }
+
+        thumbnails = [{
+            'url': thumb.get('url'),
+            'width': thumb.get('width') or None,
+            'height': thumb.get('height') or None,
+        } for thumb in traverse_obj(metadata, ('snippet', 'thumbnails', ...))]
+        self._remove_duplicate_formats(thumbnails)
+
+        return {
+            'id': video_id,
+            'title': traverse_obj(metadata, ('snippet', 'title')),
+            'timestamp': unified_timestamp(traverse_obj(metadata, ('snippet', 'activeStartDate'))),
+            'age_limit': int_or_none(traverse_obj(metadata, ('snippet', 'minimumAge'))) or 0,
+            'formats': formats,
+            'subtitles': subtitles,
+            'thumbnails': thumbnails,
+        }
diff --git a/yt_dlp/extractor/mxplayer.py b/yt_dlp/extractor/mxplayer.py
index cdc340a80..affdba10c 100644
--- a/yt_dlp/extractor/mxplayer.py
+++ b/yt_dlp/extractor/mxplayer.py
@@ -1,6 +1,10 @@
 from .common import InfoExtractor
 from ..compat import compat_str
-from ..utils import try_get
+from ..utils import (
+    int_or_none,
+    traverse_obj,
+    try_get,
+)
 
 
 class MxplayerIE(InfoExtractor):
@@ -9,6 +13,7 @@ class MxplayerIE(InfoExtractor):
         'url': 'https://www.mxplayer.in/show/watch-my-girlfriend-is-an-alien-hindi-dubbed/season-1/episode-1-online-9d2013d31d5835bb8400e3b3c5e7bb72',
         'info_dict': {
             'id': '9d2013d31d5835bb8400e3b3c5e7bb72',
+            'display_id': 'episode-1-online',
             'ext': 'mp4',
             'title': 'Episode 1',
             'description': 'md5:62ed43eb9fec5efde5cf3bd1040b7670',
@@ -17,7 +22,6 @@ class MxplayerIE(InfoExtractor):
             'duration': 2451,
             'season': 'Season 1',
             'series': 'My Girlfriend Is An Alien (Hindi Dubbed)',
-            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/9d2013d31d5835bb8400e3b3c5e7bb72/en/16x9/320x180/9562f5f8df42cad09c9a9c4e69eb1567_1920x1080.webp',
             'episode': 'Episode 1'
         },
         'params': {
@@ -28,21 +32,17 @@ class MxplayerIE(InfoExtractor):
         'url': 'https://www.mxplayer.in/movie/watch-knock-knock-hindi-dubbed-movie-online-b9fa28df3bfb8758874735bbd7d2655a?watch=true',
         'info_dict': {
             'id': 'b9fa28df3bfb8758874735bbd7d2655a',
+            'display_id': 'episode-1-online',
             'ext': 'mp4',
             'title': 'Knock Knock (Hindi Dubbed)',
-            'description': 'md5:b195ba93ff1987309cfa58e2839d2a5b',
-            'season_number': 0,
-            'episode_number': 0,
+            'description': 'md5:4160f2dfc3b87c524261366f6b736329',
             'duration': 5970,
-            'season': 'Season 0',
-            'series': None,
-            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/b9fa28df3bfb8758874735bbd7d2655a/en/16x9/320x180/test_pic1588676032011.webp',
-            'episode': 'Episode 0'
         },
         'params': {
             'format': 'bv',
             'skip_download': True,
         },
+        'skip': 'No longer available',
     }, {
         'url': 'https://www.mxplayer.in/show/watch-shaitaan/season-1/the-infamous-taxi-gang-of-meerut-online-45055d5bcff169ad48f2ad7552a83d6c',
         'info_dict': {
@@ -55,26 +55,26 @@ class MxplayerIE(InfoExtractor):
             'duration': 2332,
             'season': 'Season 1',
             'series': 'Shaitaan',
-            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/45055d5bcff169ad48f2ad7552a83d6c/en/16x9/320x180/voot_8e7d5f8d8183340869279c732c1e3a43.webp',
             'episode': 'Episode 1'
         },
         'params': {
             'format': 'best',
             'skip_download': True,
         },
+        'skip': 'No longer available.'
     }, {
         'url': 'https://www.mxplayer.in/show/watch-aashram/chapter-1/duh-swapna-online-d445579792b0135598ba1bc9088a84cb',
         'info_dict': {
             'id': 'd445579792b0135598ba1bc9088a84cb',
+            'display_id': 'duh-swapna-online',
             'ext': 'mp4',
             'title': 'Duh Swapna',
             'description': 'md5:35ff39c4bdac403c53be1e16a04192d8',
             'season_number': 1,
             'episode_number': 3,
             'duration': 2568,
-            'season': 'Chapter 1',
+            'season': 'Season 1',
             'series': 'Aashram',
-            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/d445579792b0135598ba1bc9088a84cb/en/4x3/1600x1200/test_pic1624819307993.webp',
             'episode': 'Episode 3'
         },
         'params': {
@@ -85,6 +85,7 @@ class MxplayerIE(InfoExtractor):
         'url': 'https://www.mxplayer.in/show/watch-dangerous/season-1/chapter-1-online-5a351b4f9fb69436f6bd6ae3a1a75292',
         'info_dict': {
             'id': '5a351b4f9fb69436f6bd6ae3a1a75292',
+            'display_id': 'chapter-1-online',
             'ext': 'mp4',
             'title': 'Chapter 1',
             'description': 'md5:233886b8598bc91648ac098abe1d288f',
@@ -93,7 +94,6 @@ class MxplayerIE(InfoExtractor):
             'duration': 1305,
             'season': 'Season 1',
             'series': 'Dangerous',
-            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/5a351b4f9fb69436f6bd6ae3a1a75292/en/4x3/1600x1200/test_pic1624706302350.webp',
             'episode': 'Episode 1'
         },
         'params': {
@@ -107,72 +107,79 @@ class MxplayerIE(InfoExtractor):
             'ext': 'mp4',
             'title': 'The Attacks of 26/11',
             'description': 'md5:689bacd29e97b3f31eaf519eb14127e5',
-            'season_number': 0,
-            'episode_number': 0,
             'duration': 6085,
-            'season': 'Season 0',
-            'series': None,
-            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/0452f0d80226c398d63ce7e3ea40fa2d/en/16x9/320x180/00c8955dab5e5d340dbde643f9b1f6fd_1920x1080.webp',
-            'episode': 'Episode 0'
         },
         'params': {
             'format': 'best',
             'skip_download': True,
         },
+        'skip': 'No longer available. Cannot be played on browser'
+    }, {
+        'url': 'https://www.mxplayer.in/movie/watch-kitne-door-kitne-paas-movie-online-a9e9c76c566205955f70d8b2cb88a6a2',
+        'info_dict': {
+            'id': 'a9e9c76c566205955f70d8b2cb88a6a2',
+            'display_id': 'watch-kitne-door-kitne-paas-movie-online',
+            'title': 'Kitne Door Kitne Paas',
+            'duration': 8458,
+            'ext': 'mp4',
+            'description': 'md5:fb825f3c542513088024dcafef0921b4',
+        },
+        'params': {
+            'format': 'bv',
+            'skip_download': True,
+        },
+    }, {
+        'url': 'https://www.mxplayer.in/show/watch-ek-thi-begum-hindi/season-2/game-of-power-online-5e5305c28f1409847cdc4520b6ad77cf',
+        'info_dict': {
+            'id': '5e5305c28f1409847cdc4520b6ad77cf',
+            'display_id': 'game-of-power-online',
+            'title': 'Game Of Power',
+            'duration': 1845,
+            'ext': 'mp4',
+            'description': 'md5:1d0948d2a5312d7013792d53542407f9',
+            'series': 'Ek Thi Begum (Hindi)',
+            'season': 'Season 2',
+            'season_number': 2,
+            'episode': 'Episode 2',
+            'episode_number': 2,
+        },
+        'params': {
+            'format': 'bv',
+            'skip_download': True,
+        },
     }]
 
     def _real_extract(self, url):
-        type, display_id, video_id = self._match_valid_url(url).groups()
-        type = 'movie_film' if type == 'movie' else 'tvshow_episode'
-        API_URL = 'https://androidapi.mxplay.com/v1/detail/'
-        headers = {
-            'X-Av-Code': '23',
-            'X-Country': 'IN',
-            'X-Platform': 'android',
-            'X-App-Version': '1370001318',
-            'X-Resolution': '3840x2160',
-        }
-        data_json = self._download_json(f'{API_URL}{type}/{video_id}', display_id, headers=headers)['profile']
+        video_type, display_id, video_id = self._match_valid_url(url).group('type', 'display_id', 'id')
+        if 'show' in video_type:
+            video_type = 'episode'
 
-        season, series = None, None
-        for dct in data_json.get('levelInfos', []):
-            if dct.get('type') == 'tvshow_season':
-                season = dct.get('name')
-            elif dct.get('type') == 'tvshow_show':
-                series = dct.get('name')
-        thumbnails = []
-        for thumb in data_json.get('poster', []):
-            thumbnails.append({
-                'url': thumb.get('url'),
-                'width': thumb.get('width'),
-                'height': thumb.get('height'),
-            })
+        data_json = self._download_json(
+            f'https://api.mxplay.com/v1/web/detail/video?type={video_type}&id={video_id}', display_id)
 
-        formats = []
-        subtitles = {}
-        for dct in data_json.get('playInfo', []):
-            if dct.get('extension') == 'mpd':
-                frmt, subs = self._extract_mpd_formats_and_subtitles(dct.get('playUrl'), display_id, fatal=False)
-                formats.extend(frmt)
-                subtitles = self._merge_subtitles(subtitles, subs)
-            elif dct.get('extension') == 'm3u8':
-                frmt, subs = self._extract_m3u8_formats_and_subtitles(dct.get('playUrl'), display_id, fatal=False)
-                formats.extend(frmt)
-                subtitles = self._merge_subtitles(subtitles, subs)
+        streams = traverse_obj(data_json, ('stream', {'m3u8': ('hls', 'high'), 'mpd': ('dash', 'high')}))
+        formats, dash_subs = self._extract_mpd_formats_and_subtitles(
+            f'https://llvod.mxplay.com/{streams["mpd"]}', display_id, fatal=False)
+        hls_frmts, hls_subs = self._extract_m3u8_formats_and_subtitles(
+            f'https://llvod.mxplay.com/{streams["m3u8"]}', display_id, fatal=False)
+
+        formats.extend(hls_frmts)
         self._sort_formats(formats)
+
+        season = traverse_obj(data_json, ('container', 'title'))
         return {
             'id': video_id,
+            'title': data_json.get('title'),
+            'formats': formats,
+            'subtitles': self._merge_subtitles(dash_subs, hls_subs),
             'display_id': display_id,
-            'title': data_json.get('name') or display_id,
-            'description': data_json.get('description'),
-            'season_number': data_json.get('seasonNum'),
-            'episode_number': data_json.get('episodeNum'),
             'duration': data_json.get('duration'),
+            'series': traverse_obj(data_json, ('container', 'container', 'title')),
+            'description': data_json.get('description'),
             'season': season,
-            'series': series,
-            'thumbnails': thumbnails,
-            'formats': formats,
-            'subtitles': subtitles,
+            'season_number': int_or_none(
+                self._search_regex(r'Season (\d+)', season, 'Season Number', default=None)),
+            'episode_number': data_json.get('sequence') or None,
         }
 
 
diff --git a/yt_dlp/extractor/nbc.py b/yt_dlp/extractor/nbc.py
index 910cbedf6..6b482620a 100644
--- a/yt_dlp/extractor/nbc.py
+++ b/yt_dlp/extractor/nbc.py
@@ -7,14 +7,20 @@
 from .adobepass import AdobePassIE
 from ..compat import compat_urllib_parse_unquote
 from ..utils import (
+    ExtractorError,
     int_or_none,
     parse_age_limit,
     parse_duration,
     RegexNotFoundError,
     smuggle_url,
+    str_or_none,
+    traverse_obj,
     try_get,
+    unified_strdate,
     unified_timestamp,
     update_url_query,
+    url_basename,
+    variadic,
 )
 
 
@@ -584,3 +590,169 @@ def _real_extract(self, url):
             'formats': formats,
             'is_live': is_live,
         }
+
+
+class NBCStationsIE(InfoExtractor):
+    _DOMAIN_RE = '|'.join(map(re.escape, (
+        'nbcbayarea', 'nbcboston', 'nbcchicago', 'nbcconnecticut', 'nbcdfw', 'nbclosangeles',
+        'nbcmiami', 'nbcnewyork', 'nbcphiladelphia', 'nbcsandiego', 'nbcwashington',
+        'necn', 'telemundo52', 'telemundoarizona', 'telemundochicago', 'telemundonuevainglaterra',
+    )))
+    _VALID_URL = rf'https?://(?:www\.)?(?P<site>{_DOMAIN_RE})\.com/(?:[^/?#]+/)*(?P<id>[^/?#]+)/?(?:$|[#?])'
+
+    _TESTS = [{
+        'url': 'https://www.nbclosangeles.com/news/local/large-structure-fire-in-downtown-la-prompts-smoke-odor-advisory/2968618/',
+        'md5': '462041d91bd762ef5a38b7d85d6dc18f',
+        'info_dict': {
+            'id': '2968618',
+            'ext': 'mp4',
+            'title': 'Large Structure Fire in Downtown LA Prompts Smoke Odor Advisory',
+            'description': None,
+            'timestamp': 1661135892,
+            'upload_date': '20220821',
+            'uploader': 'NBC 4',
+            'uploader_id': 'KNBC',
+            'channel': 'nbclosangeles',
+        },
+    }, {
+        'url': 'https://www.telemundoarizona.com/responde/huracan-complica-reembolso-para-televidente-de-tucson/2247002/',
+        'md5': '0917dcf7885be1023a9220630d415f67',
+        'info_dict': {
+            'id': '2247002',
+            'ext': 'mp4',
+            'title': 'Huracán complica que televidente de Tucson reciba reembolso',
+            'description': 'md5:af298dc73aab74d4fca6abfb12acb6cf',
+            'timestamp': 1660886507,
+            'upload_date': '20220819',
+            'uploader': 'Telemundo Arizona',
+            'uploader_id': 'KTAZ',
+            'channel': 'telemundoarizona',
+        },
+    }]
+
+    _RESOLUTIONS = {
+        '1080': '1920',
+        '720': '1280',
+        '540': '960',
+        '360': '640',
+        '234': '416',
+    }
+
+    def _real_extract(self, url):
+        channel, video_id = self._match_valid_url(url).group('site', 'id')
+        webpage = self._download_webpage(url, video_id)
+
+        nbc_data = self._search_json(
+            r'<script>var\s*nbc\s*=\s*', webpage, 'NBC JSON data', video_id)
+        pdk_acct = nbc_data.get('pdkAcct') or 'Yh1nAC'
+        fw_ssid = traverse_obj(nbc_data, ('video', 'fwSSID'))
+        fw_network_id = traverse_obj(nbc_data, ('video', 'fwNetworkID'), default='382114')
+
+        video_data = self._parse_json(self._html_search_regex(
+            r'data-videos="([^"]*)"', webpage, 'video data', default='{}'), video_id)
+        video_data = variadic(video_data)[0]
+        video_data.update(self._parse_json(self._html_search_regex(
+            r'data-meta="([^"]*)"', webpage, 'metadata', default='{}'), video_id))
+
+        formats = []
+
+        if video_data.get('mpx_is_livestream') == '1':
+            live = True
+            player_id = traverse_obj(
+                video_data, 'mpx_m3upid', ('video', 'meta', 'mpx_m3upid'), 'mpx_pid',
+                ('video', 'meta', 'mpx_pid'), 'pid_streaming_web_medium')
+            query = {
+                'mbr': 'true',
+                'assetTypes': 'LegacyRelease',
+                'fwsitesection': fw_ssid,
+                'fwNetworkID': fw_network_id,
+                'pprofile': 'ots_desktop_html',
+                'sensitive': 'false',
+                'w': '1920',
+                'h': '1080',
+                'rnd': '1660303',
+                'mode': 'LIVE',
+                'format': 'SMIL',
+                'tracking': 'true',
+                'formats': 'M3U+none,MPEG-DASH+none,MPEG4,MP3',
+                'vpaid': 'script',
+                'schema': '2.0',
+                'SDK': 'PDK+6.1.3',
+            }
+            info = {
+                'title': f'{channel} livestream',
+            }
+
+        else:
+            live = False
+            player_id = traverse_obj(
+                video_data, ('video', 'meta', 'pid_streaming_web_high'), 'pid_streaming_web_high',
+                ('video', 'meta', 'mpx_pid'), 'mpx_pid')
+
+            date_string = traverse_obj(video_data, 'date_string', 'date_gmt')
+            if date_string:
+                date_string = self._search_regex(
+                    r'datetime="([^"]+)"', date_string, 'date string', fatal=False)
+            else:
+                date_string = traverse_obj(
+                    nbc_data, ('dataLayer', 'adobe', 'prop70'), ('dataLayer', 'adobe', 'eVar70'),
+                    ('dataLayer', 'adobe', 'eVar59'))
+
+            video_url = traverse_obj(video_data, ('video', 'meta', 'mp4_url'), 'mp4_url')
+            if video_url:
+                height = url_basename(video_url).split('-')[1].split('p')[0]
+                formats.append({
+                    'url': video_url,
+                    'ext': 'mp4',
+                    'width': int_or_none(self._RESOLUTIONS.get(height)),
+                    'height': int_or_none(height),
+                    'format_id': f'http-{height}',
+                })
+
+            query = {
+                'mbr': 'true',
+                'assetTypes': 'LegacyRelease',
+                'fwsitesection': fw_ssid,
+                'fwNetworkID': fw_network_id,
+                'format': 'redirect',
+                'manifest': 'm3u',
+                'Tracking': 'true',
+                'Embedded': 'true',
+                'formats': 'MPEG4',
+            }
+            info = {
+                'title': video_data.get('title') or traverse_obj(
+                    nbc_data, ('dataLayer', 'contenttitle'), ('dataLayer', 'title'),
+                    ('dataLayer', 'adobe', 'prop22'), ('dataLayer', 'id')),
+                'description': traverse_obj(video_data, 'summary', 'excerpt', 'video_hero_text'),
+                'upload_date': str_or_none(unified_strdate(date_string)),
+                'timestamp': int_or_none(unified_timestamp(date_string)),
+            }
+
+        if not player_id:
+            raise ExtractorError(
+                'No video player ID or livestream player ID found in webpage', expected=True)
+
+        headers = {'Origin': f'https://www.{channel}.com'}
+        manifest, urlh = self._download_webpage_handle(
+            f'https://link.theplatform.com/s/{pdk_acct}/{player_id}', video_id,
+            headers=headers, query=query, note='Downloading manifest')
+        if live:
+            manifest_url = self._search_regex(r'<video src="([^"]*)', manifest, 'manifest URL')
+        else:
+            manifest_url = urlh.geturl()
+
+        formats.extend(self._extract_m3u8_formats(
+            manifest_url, video_id, 'mp4', headers=headers, m3u8_id='hls',
+            fatal=live, live=live, errnote='No HLS formats found'))
+        self._sort_formats(formats)
+
+        return {
+            'id': str_or_none(video_id),
+            'channel': channel,
+            'uploader': str_or_none(nbc_data.get('on_air_name')),
+            'uploader_id': str_or_none(nbc_data.get('callLetters')),
+            'formats': formats,
+            'is_live': live,
+            **info,
+        }
diff --git a/yt_dlp/extractor/radiofrance.py b/yt_dlp/extractor/radiofrance.py
index 7b60b2617..38420a15d 100644
--- a/yt_dlp/extractor/radiofrance.py
+++ b/yt_dlp/extractor/radiofrance.py
@@ -84,7 +84,7 @@ def _real_extract(self, url):
         webpage = self._download_webpage(url, display_id)
 
         # _search_json_ld doesn't correctly handle this. See https://github.com/yt-dlp/yt-dlp/pull/3874#discussion_r891903846
-        video_data = self._search_json('', webpage, 'audio data', display_id, contains_pattern=r'\s*"@type"\s*:\s*"AudioObject"\s*.+')
+        video_data = self._search_json('', webpage, 'audio data', display_id, contains_pattern=r'{\s*"@type"\s*:\s*"AudioObject".+}')
 
         return {
             'id': video_id,
diff --git a/yt_dlp/extractor/tencent.py b/yt_dlp/extractor/tencent.py
index c755407d3..44cd19600 100644
--- a/yt_dlp/extractor/tencent.py
+++ b/yt_dlp/extractor/tencent.py
@@ -262,6 +262,41 @@ def _get_webpage_metadata(self, webpage, video_id):
             traverse_obj(self._search_nextjs_data(webpage, video_id), ('props', 'pageProps', 'data')),
             video_id, fatal=False)
 
+    def _extract_episode(self, url):
+        video_id, series_id = self._match_valid_url(url).group('id', 'series_id')
+        webpage = self._download_webpage(url, video_id)
+        webpage_metadata = self._get_webpage_metadata(webpage, video_id)
+
+        formats, subtitles = self._extract_all_video_formats_and_subtitles(url, video_id, series_id)
+        return {
+            'id': video_id,
+            'title': self._get_clean_title(self._og_search_title(webpage)
+                                           or traverse_obj(webpage_metadata, ('coverInfo', 'title'))),
+            'description': (traverse_obj(webpage_metadata, ('coverInfo', 'description'))
+                            or self._og_search_description(webpage)),
+            'formats': formats,
+            'subtitles': subtitles,
+            'thumbnail': self._og_search_thumbnail(webpage),
+            'duration': int_or_none(traverse_obj(webpage_metadata, ('videoInfo', 'duration'))),
+            'series': traverse_obj(webpage_metadata, ('coverInfo', 'title')),
+            'episode_number': int_or_none(traverse_obj(webpage_metadata, ('videoInfo', 'episode'))),
+        }
+
+    def _extract_series(self, url, ie):
+        series_id = self._match_id(url)
+        webpage = self._download_webpage(url, series_id)
+        webpage_metadata = self._get_webpage_metadata(webpage, series_id)
+
+        episode_paths = ([f'/play/{series_id}/{episode["vid"]}' for episode in webpage_metadata.get('videoList')]
+                         or re.findall(r'<a[^>]+class="play-video__link"[^>]+href="(?P<path>[^"]+)', webpage))
+
+        return self.playlist_from_matches(
+            episode_paths, series_id, ie=ie, getter=functools.partial(urljoin, url),
+            title=self._get_clean_title(traverse_obj(webpage_metadata, ('coverInfo', 'title'))
+                                        or self._og_search_title(webpage)),
+            description=(traverse_obj(webpage_metadata, ('coverInfo', 'description'))
+                         or self._og_search_description(webpage)))
+
 
 class WeTvEpisodeIE(WeTvBaseIE):
     IE_NAME = 'wetv:episode'
@@ -312,24 +347,7 @@ class WeTvEpisodeIE(WeTvBaseIE):
     }]
 
     def _real_extract(self, url):
-        video_id, series_id = self._match_valid_url(url).group('id', 'series_id')
-        webpage = self._download_webpage(url, video_id)
-        webpage_metadata = self._get_webpage_metadata(webpage, video_id)
-
-        formats, subtitles = self._extract_all_video_formats_and_subtitles(url, video_id, series_id)
-        return {
-            'id': video_id,
-            'title': self._get_clean_title(self._og_search_title(webpage)
-                                           or traverse_obj(webpage_metadata, ('coverInfo', 'title'))),
-            'description': (traverse_obj(webpage_metadata, ('coverInfo', 'description'))
-                            or self._og_search_description(webpage)),
-            'formats': formats,
-            'subtitles': subtitles,
-            'thumbnail': self._og_search_thumbnail(webpage),
-            'duration': int_or_none(traverse_obj(webpage_metadata, ('videoInfo', 'duration'))),
-            'series': traverse_obj(webpage_metadata, ('coverInfo', 'title')),
-            'episode_number': int_or_none(traverse_obj(webpage_metadata, ('videoInfo', 'episode'))),
-        }
+        return self._extract_episode(url)
 
 
 class WeTvSeriesIE(WeTvBaseIE):
@@ -354,16 +372,77 @@ class WeTvSeriesIE(WeTvBaseIE):
     }]
 
     def _real_extract(self, url):
-        series_id = self._match_id(url)
-        webpage = self._download_webpage(url, series_id)
-        webpage_metadata = self._get_webpage_metadata(webpage, series_id)
+        return self._extract_series(url, WeTvEpisodeIE)
 
-        episode_paths = ([f'/play/{series_id}/{episode["vid"]}' for episode in webpage_metadata.get('videoList')]
-                         or re.findall(r'<a[^>]+class="play-video__link"[^>]+href="(?P<path>[^"]+)', webpage))
 
-        return self.playlist_from_matches(
-            episode_paths, series_id, ie=WeTvEpisodeIE, getter=functools.partial(urljoin, url),
-            title=self._get_clean_title(traverse_obj(webpage_metadata, ('coverInfo', 'title'))
-                                        or self._og_search_title(webpage)),
-            description=(traverse_obj(webpage_metadata, ('coverInfo', 'description'))
-                         or self._og_search_description(webpage)))
+class IflixBaseIE(WeTvBaseIE):
+    _VALID_URL_BASE = r'https?://(?:www\.)?iflix\.com/(?:[^?#]+/)?play'
+
+    _API_URL = 'https://vplay.iflix.com/getvinfo'
+    _APP_VERSION = '3.5.57'
+    _PLATFORM = '330201'
+    _HOST = 'www.iflix.com'
+    _REFERER = 'www.iflix.com'
+
+
+class IflixEpisodeIE(IflixBaseIE):
+    IE_NAME = 'iflix:episode'
+    _VALID_URL = IflixBaseIE._VALID_URL_BASE + r'/(?P<series_id>\w+)(?:-[^?#]+)?/(?P<id>\w+)(?:-[^?#]+)?'
+
+    _TESTS = [{
+        'url': 'https://www.iflix.com/en/play/daijrxu03yypu0s/a0040kvgaza',
+        'md5': '9740f9338c3a2105290d16b68fb3262f',
+        'info_dict': {
+            'id': 'a0040kvgaza',
+            'ext': 'mp4',
+            'title': 'EP1: Put Your Head On My Shoulder 2021',
+            'description': 'md5:c095a742d3b7da6dfedd0c8170727a42',
+            'thumbnail': r're:^https?://[^?#]+daijrxu03yypu0s',
+            'series': 'Put Your Head On My Shoulder 2021',
+            'episode': 'Episode 1',
+            'episode_number': 1,
+            'duration': 2639,
+        },
+    }, {
+        'url': 'https://www.iflix.com/en/play/fvvrcc3ra9lbtt1-Take-My-Brother-Away/i0029sd3gm1-EP1%EF%BC%9ATake-My-Brother-Away',
+        'md5': '375c9b8478fdedca062274b2c2f53681',
+        'info_dict': {
+            'id': 'i0029sd3gm1',
+            'ext': 'mp4',
+            'title': 'EP1：Take My Brother Away',
+            'description': 'md5:f0f7be1606af51cd94d5627de96b0c76',
+            'thumbnail': r're:^https?://[^?#]+fvvrcc3ra9lbtt1',
+            'series': 'Take My Brother Away',
+            'episode': 'Episode 1',
+            'episode_number': 1,
+            'duration': 228,
+        },
+    }]
+
+    def _real_extract(self, url):
+        return self._extract_episode(url)
+
+
+class IflixSeriesIE(IflixBaseIE):
+    _VALID_URL = IflixBaseIE._VALID_URL_BASE + r'/(?P<id>\w+)(?:-[^/?#]+)?/?(?:[?#]|$)'
+
+    _TESTS = [{
+        'url': 'https://www.iflix.com/en/play/g21a6qk4u1s9x22-You-Are-My-Hero',
+        'info_dict': {
+            'id': 'g21a6qk4u1s9x22',
+            'title': 'You Are My Hero',
+            'description': 'md5:9c4d844bc0799cd3d2b5aed758a2050a',
+        },
+        'playlist_count': 40,
+    }, {
+        'url': 'https://www.iflix.com/play/0s682hc45t0ohll',
+        'info_dict': {
+            'id': '0s682hc45t0ohll',
+            'title': 'Miss Gu Who Is Silent',
+            'description': 'md5:a9651d0236f25af06435e845fa2f8c78',
+        },
+        'playlist_count': 20,
+    }]
+
+    def _real_extract(self, url):
+        return self._extract_series(url, IflixEpisodeIE)
diff --git a/yt_dlp/extractor/tennistv.py b/yt_dlp/extractor/tennistv.py
index 3bd7ce3c4..5baa21d52 100644
--- a/yt_dlp/extractor/tennistv.py
+++ b/yt_dlp/extractor/tennistv.py
@@ -148,7 +148,7 @@ def _real_extract(self, url):
                 webpage, 'description', fatal=False),
             'thumbnail': f'https://open.http.mp.streamamg.com/p/{self._PARTNER_ID}/sp/{self._PARTNER_ID}00/thumbnail/entry_id/{entryid}/version/100001/height/1920',
             'timestamp': unified_timestamp(self._html_search_regex(
-                r'<span itemprop="description" content=["\']([^"\']+)["\']>', webpage, 'upload time')),
+                r'<span itemprop="uploadDate" content=["\']([^"\']+)["\']>', webpage, 'upload time', fatal=False)),
             'series': self._html_search_regex(r'data-series\s*?=\s*?"(.*?)"', webpage, 'series', fatal=False) or None,
             'season': self._html_search_regex(r'data-tournament-city\s*?=\s*?"(.*?)"', webpage, 'season', fatal=False) or None,
             'episode': self._html_search_regex(r'data-round\s*?=\s*?"(.*?)"', webpage, 'round', fatal=False) or None,
diff --git a/yt_dlp/extractor/trovo.py b/yt_dlp/extractor/trovo.py
index c8816f7bc..f4d4bcd17 100644
--- a/yt_dlp/extractor/trovo.py
+++ b/yt_dlp/extractor/trovo.py
@@ -7,6 +7,7 @@
 from ..utils import (
     ExtractorError,
     format_field,
+    traverse_obj,
     int_or_none,
     str_or_none,
     try_get,
@@ -26,7 +27,7 @@ def _call_api(self, video_id, data):
         resp = self._download_json(
             url, video_id, data=json.dumps([data]).encode(), headers={'Accept': 'application/json'},
             query={
-                'qid': ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)),
+                'qid': ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)),
             })[0]
         if 'errors' in resp:
             raise ExtractorError(f'Trovo said: {resp["errors"][0]["message"]}')
@@ -146,7 +147,26 @@ class TrovoVodIE(TrovoBaseIE):
             'upload_date': '20220611',
             'comment_count': int,
             'categories': ['Minecraft'],
-        }
+        },
+        'skip': 'Not available',
+    }, {
+        'url': 'https://trovo.live/s/Trovo/549756886599?vid=ltv-100264059_100264059_387702304241698583',
+        'info_dict': {
+            'id': 'ltv-100264059_100264059_387702304241698583',
+            'ext': 'mp4',
+            'timestamp': 1661479563,
+            'thumbnail': 'http://vod.trovo.live/be5ae591vodtransusw1301120758/cccb9915387702304241698583/coverBySnapshot/coverBySnapshot_10_0.jpg',
+            'uploader_id': '100264059',
+            'uploader': 'Trovo',
+            'title': 'Dev Corner 8/25',
+            'uploader_url': 'https://trovo.live/Trovo',
+            'duration': 3753,
+            'view_count': int,
+            'like_count': int,
+            'upload_date': '20220826',
+            'comment_count': int,
+            'categories': ['Talk Shows'],
+        },
     }, {
         'url': 'https://trovo.live/video/ltv-100095501_100095501_1609596043',
         'only_matching': True,
@@ -162,22 +182,20 @@ def _real_extract(self, url):
         # however that seems unreliable - sometimes it randomly doesn't return the data,
         # at least when using a non-residential IP.
         resp = self._call_api(vid, data={
-            'operationName': 'batchGetVodDetailInfo',
+            'operationName': 'vod_VodReaderService_BatchGetVodDetailInfo',
             'variables': {
                 'params': {
                     'vids': [vid],
                 },
             },
-            'extensions': {
-                'persistedQuery': {
-                    'version': 1,
-                    'sha256Hash': 'ceae0355d66476e21a1dd8e8af9f68de95b4019da2cda8b177c9a2255dad31d0',
-                },
-            },
+            'extensions': {},
         })
-        vod_detail_info = resp['VodDetailInfos'][vid]
-        vod_info = vod_detail_info['vodInfo']
-        title = vod_info['title']
+
+        vod_detail_info = traverse_obj(resp, ('VodDetailInfos', vid), expected_type=dict)
+        if not vod_detail_info:
+            raise ExtractorError('This video not found or not available anymore', expected=True)
+        vod_info = vod_detail_info.get('vodInfo')
+        title = vod_info.get('title')
 
         if try_get(vod_info, lambda x: x['playbackRights']['playbackRights'] != 'Normal'):
             playback_rights_setting = vod_info['playbackRights']['playbackRightsSetting']
@@ -228,7 +246,7 @@ def _real_extract(self, url):
     def _get_comments(self, vid):
         for page in itertools.count(1):
             comments_json = self._call_api(vid, data={
-                'operationName': 'getCommentList',
+                'operationName': 'public_CommentProxyService_GetCommentList',
                 'variables': {
                     'params': {
                         'appInfo': {
@@ -240,10 +258,7 @@ def _get_comments(self, vid):
                     },
                 },
                 'extensions': {
-                    'persistedQuery': {
-                        'version': 1,
-                        'sha256Hash': 'be8e5f9522ddac7f7c604c0d284fd22481813263580849926c4c66fb767eed25',
-                    },
+                    'singleReq': 'true',
                 },
             })
             for comment in comments_json['commentList']:
@@ -266,33 +281,37 @@ def _get_comments(self, vid):
 
 
 class TrovoChannelBaseIE(TrovoBaseIE):
-    def _get_vod_json(self, page, uid):
-        raise NotImplementedError('This method must be implemented by subclasses')
-
-    def _entries(self, uid):
+    def _entries(self, spacename):
         for page in itertools.count(1):
-            vod_json = self._get_vod_json(page, uid)
+            vod_json = self._call_api(spacename, data={
+                'operationName': self._OPERATION,
+                'variables': {
+                    'params': {
+                        'terminalSpaceID': {
+                            'spaceName': spacename,
+                        },
+                        'currPage': page,
+                        'pageSize': 99,
+                    },
+                },
+                'extensions': {
+                    'singleReq': 'true',
+                },
+            })
             vods = vod_json.get('vodInfos', [])
             for vod in vods:
+                vid = vod.get('vid')
+                room = traverse_obj(vod, ('spaceInfo', 'roomID'))
                 yield self.url_result(
-                    'https://trovo.live/%s/%s' % (self._TYPE, vod.get('vid')),
+                    f'https://trovo.live/s/{spacename}/{room}?vid={vid}',
                     ie=TrovoVodIE.ie_key())
-            has_more = vod_json['hasMore']
+            has_more = vod_json.get('hasMore')
             if not has_more:
                 break
 
     def _real_extract(self, url):
-        id = self._match_id(url)
-        live_info = self._call_api(id, data={
-            'operationName': 'live_LiveReaderService_GetLiveInfo',
-            'variables': {
-                'params': {
-                    'userName': id,
-                },
-            },
-        })
-        uid = str(live_info['streamerInfo']['uid'])
-        return self.playlist_result(self._entries(uid), playlist_id=uid)
+        spacename = self._match_id(url)
+        return self.playlist_result(self._entries(spacename), playlist_id=spacename)
 
 
 class TrovoChannelVodIE(TrovoChannelBaseIE):
@@ -303,29 +322,11 @@ class TrovoChannelVodIE(TrovoChannelBaseIE):
         'url': 'trovovod:OneTappedYou',
         'playlist_mincount': 24,
         'info_dict': {
-            'id': '100719456',
+            'id': 'OneTappedYou',
         },
     }]
 
-    _TYPE = 'video'
-
-    def _get_vod_json(self, page, uid):
-        return self._call_api(uid, data={
-            'operationName': 'getChannelLtvVideoInfos',
-            'variables': {
-                'params': {
-                    'channelID': int(uid),
-                    'pageSize': 99,
-                    'currPage': page,
-                },
-            },
-            'extensions': {
-                'persistedQuery': {
-                    'version': 1,
-                    'sha256Hash': '78fe32792005eab7e922cafcdad9c56bed8bbc5f5df3c7cd24fcb84a744f5f78',
-                },
-            },
-        })
+    _OPERATION = 'vod_VodReaderService_GetChannelLtvVideoInfos'
 
 
 class TrovoChannelClipIE(TrovoChannelBaseIE):
@@ -336,26 +337,8 @@ class TrovoChannelClipIE(TrovoChannelBaseIE):
         'url': 'trovoclip:OneTappedYou',
         'playlist_mincount': 29,
         'info_dict': {
-            'id': '100719456',
+            'id': 'OneTappedYou',
         },
     }]
 
-    _TYPE = 'clip'
-
-    def _get_vod_json(self, page, uid):
-        return self._call_api(uid, data={
-            'operationName': 'getChannelClipVideoInfos',
-            'variables': {
-                'params': {
-                    'channelID': int(uid),
-                    'pageSize': 99,
-                    'currPage': page,
-                },
-            },
-            'extensions': {
-                'persistedQuery': {
-                    'version': 1,
-                    'sha256Hash': 'e7924bfe20059b5c75fc8ff9e7929f43635681a7bdf3befa01072ed22c8eff31',
-                },
-            },
-        })
+    _OPERATION = 'vod_VodReaderService_GetChannelClipVideoInfos'
diff --git a/yt_dlp/extractor/tv2.py b/yt_dlp/extractor/tv2.py
index 391baa6c5..0024f7241 100644
--- a/yt_dlp/extractor/tv2.py
+++ b/yt_dlp/extractor/tv2.py
@@ -16,23 +16,27 @@
 
 
 class TV2IE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?tv2\.no/v\d*/(?P<id>\d+)'
+    _VALID_URL = r'https?://(?:www\.)?tv2\.no/v(?:ideo)?\d*/(?:[^?#]+/)*(?P<id>\d+)'
     _TESTS = [{
-        'url': 'http://www.tv2.no/v/916509/',
+        'url': 'http://www.tv2.no/v/1791207/',
         'info_dict': {
-            'id': '916509',
+            'id': '1791207',
             'ext': 'mp4',
-            'title': 'Se Frode Gryttens hyllest av Steven Gerrard',
-            'description': 'TV 2 Sportens huspoet tar avskjed med Liverpools kaptein Steven Gerrard.',
-            'timestamp': 1431715610,
-            'upload_date': '20150515',
-            'duration': 157,
+            'title': 'Her kolliderer romsonden med asteroiden ',
+            'description': 'En romsonde har krasjet inn i en asteroide i verdensrommet. Kollisjonen skjedde klokken 01:14 natt til tirsdag 27. september norsk tid. \n\nNasa kaller det sitt første forsøk på planetforsvar.',
+            'timestamp': 1664238190,
+            'upload_date': '20220927',
+            'duration': 146,
+            'thumbnail': r're:^https://.*$',
             'view_count': int,
             'categories': list,
         },
     }, {
         'url': 'http://www.tv2.no/v2/916509',
         'only_matching': True,
+    }, {
+        'url': 'https://www.tv2.no/video/nyhetene/her-kolliderer-romsonden-med-asteroiden/1791207/',
+        'only_matching': True,
     }]
     _PROTOCOLS = ('HLS', 'DASH')
     _GEO_COUNTRIES = ['NO']
@@ -114,13 +118,13 @@ def _real_extract(self, url):
 
 
 class TV2ArticleIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?tv2\.no/(?:a|\d{4}/\d{2}/\d{2}(/[^/]+)+)/(?P<id>\d+)'
+    _VALID_URL = r'https?://(?:www\.)?tv2\.no/(?!v(?:ideo)?\d*/)[^?#]+/(?P<id>\d+)'
     _TESTS = [{
-        'url': 'http://www.tv2.no/2015/05/16/nyheter/alesund/krim/pingvin/6930542',
+        'url': 'https://www.tv2.no/underholdning/forraeder/katarina-flatland-angrer-etter-forraeder-exit/15095188/',
         'info_dict': {
-            'id': '6930542',
-            'title': 'Russen hetses etter pingvintyveri - innrømmer å ha åpnet luken på buret',
-            'description': 'De fire siktede nekter fortsatt for å ha stjålet pingvinbabyene, men innrømmer å ha åpnet luken til de små kyllingene.',
+            'id': '15095188',
+            'title': 'Katarina Flatland angrer etter Forræder-exit',
+            'description': 'SANDEFJORD (TV 2): Katarina Flatland (33) måtte følge i sine fars fotspor, da hun ble forvist fra Forræder.',
         },
         'playlist_count': 2,
     }, {
@@ -138,7 +142,7 @@ def _real_extract(self, url):
 
         if not assets:
             # New embed pattern
-            for v in re.findall(r'(?s)TV2ContentboxVideo\(({.+?})\)', webpage):
+            for v in re.findall(r'(?s)(?:TV2ContentboxVideo|TV2\.TV2Video)\(({.+?})\)', webpage):
                 video = self._parse_json(
                     v, playlist_id, transform_source=js_to_json, fatal=False)
                 if not video:
diff --git a/yt_dlp/extractor/unscripted.py b/yt_dlp/extractor/unscripted.py
new file mode 100644
index 000000000..6643a71b1
--- /dev/null
+++ b/yt_dlp/extractor/unscripted.py
@@ -0,0 +1,53 @@
+from .common import InfoExtractor
+from ..utils import parse_duration, traverse_obj
+
+
+class UnscriptedNewsVideoIE(InfoExtractor):
+    _VALID_URL = r'https?://www\.unscripted\.news/videos/(?P<id>[\w-]+)'
+    _TESTS = [{
+        'url': 'https://www.unscripted.news/videos/a-day-at-the-farmers-protest',
+        'info_dict': {
+            'id': '60c0a55cd1e99b1079918a57',
+            'display_id': 'a-day-at-the-farmers-protest',
+            'ext': 'mp4',
+            'title': 'A Day at the Farmers\' Protest',
+            'description': 'md5:4b3df22747a03e8f14f746dd72190384',
+            'thumbnail': 'https://s3.unscripted.news/anj2/60c0a55cd1e99b1079918a57/5f199a65-c803-4a5c-8fce-2077359c3b72.jpg',
+            'duration': 2251.0,
+            'series': 'Ground Reports',
+        }
+    }, {
+        'url': 'https://www.unscripted.news/videos/you-get-the-politicians-you-deserve-ft-shashi-tharoor',
+        'info_dict': {
+            'id': '5fb3afbf18ac817d341a74d8',
+            'display_id': 'you-get-the-politicians-you-deserve-ft-shashi-tharoor',
+            'ext': 'mp4',
+            'cast': ['Avalok Langer', 'Ashwin Mehta'],
+            'thumbnail': 'https://s3.unscripted.news/anj2/5fb3afbf18ac817d341a74d8/82bd7942-4f20-4cd8-98ae-83f9e814f998.jpg',
+            'description': 'md5:1e91b069238a705ca3a40f87e6f1182c',
+            'duration': 1046.0,
+            'series': 'Dumb Questions Only',
+            'title': 'You Get The Politicians You Deserve! ft. Shashi Tharoor',
+        }
+    }]
+
+    def _real_extract(self, url):
+        display_id = self._match_id(url)
+        webpage = self._download_webpage(url, display_id)
+        nextjs_data = self._search_nextjs_data(webpage, display_id)['props']['pageProps']['dataLocal']
+
+        # TODO: get subtitle from srt key
+        formats, subtitles = self._extract_m3u8_formats_and_subtitles(nextjs_data['alt_content'], display_id)
+
+        return {
+            'id': nextjs_data['_id'],
+            'display_id': display_id,
+            'title': nextjs_data.get('title') or self._og_search_title(webpage),
+            'description': nextjs_data.get('sh_heading') or self._og_search_description(webpage),
+            'formats': formats,
+            'subtitles': subtitles,
+            'thumbnail': self._og_search_thumbnail(webpage),
+            'duration': parse_duration(nextjs_data.get('duration')),
+            'series': traverse_obj(nextjs_data, ('show', 'topic')),
+            'cast': traverse_obj(nextjs_data, ('cast_crew', ..., 'displayname')),
+        }
diff --git a/yt_dlp/extractor/yandexvideo.py b/yt_dlp/extractor/yandexvideo.py
index 37ff514b3..eadb1aaee 100644
--- a/yt_dlp/extractor/yandexvideo.py
+++ b/yt_dlp/extractor/yandexvideo.py
@@ -147,7 +147,7 @@ def _real_extract(self, url):
 
 
 class YandexVideoPreviewIE(InfoExtractor):
-    _VALID_URL = r'https?://(?:www\.)?yandex\.ru/video/preview(?:/?\?.*?filmId=|/)(?P<id>\d+)'
+    _VALID_URL = r'https?://(?:www\.)?yandex\.\w{2,3}(?:\.(?:am|ge|il|tr))?/video/preview(?:/?\?.*?filmId=|/)(?P<id>\d+)'
     _TESTS = [{  # Odnoklassniki
         'url': 'https://yandex.ru/video/preview/?filmId=10682852472978372885&text=summer',
         'info_dict': {
@@ -174,6 +174,9 @@ class YandexVideoPreviewIE(InfoExtractor):
     }, {  # Odnoklassniki
         'url': 'https://yandex.ru/video/preview/?text=Francis%20Lai%20-%20Le%20Bon%20Et%20Les%20MC)chants&path=wizard&parent-reqid=1643208087979310-1481782809207673478-sas3-0931-2f9-sas-l7-balancer-8080-BAL-9380&wiz_type=vital&filmId=12508152936505397283',
         'only_matching': True,
+    }, {  # Odnoklassniki
+        'url': 'https://yandex.com/video/preview/?text=dossier%2051%20film%201978&path=yandex_search&parent-reqid=1664361087754492-8727541069609384458-sas2-0340-sas-l7-balancer-8080-BAL-8045&noreask=1&from_type=vast&filmId=5794987234584444632',
+        'only_matching': True,
     }]
 
     def _real_extract(self, url):
diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py
index 3e2ce8434..6cba9299a 100644
--- a/yt_dlp/utils.py
+++ b/yt_dlp/utils.py
@@ -3546,7 +3546,7 @@ def get_compatible_ext(*, vcodecs, acodecs, vexts, aexts, preferences=None):
     COMPATIBLE_CODECS = {
         'mp4': {
             'av1', 'hevc', 'avc1', 'mp4a',  # fourcc (m3u8, mpd)
-            'h264', 'aacl',  # Set in ISM
+            'h264', 'aacl', 'ec-3',  # Set in ISM
         },
         'webm': {
             'av1', 'vp9', 'vp8', 'opus', 'vrbs',


Test patch:
diff --git a/test/test_InfoExtractor.py b/test/test_InfoExtractor.py
index f57a29ffc..016a2ac7f 100644
--- a/test/test_InfoExtractor.py
+++ b/test/test_InfoExtractor.py
@@ -1567,6 +1567,292 @@ def test_parse_ism_formats(self):
                     ]
                 },
             ),
+            (
+                'ec-3_test',
+                'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                [{
+                    'format_id': 'audio_deu_1-224',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'isma',
+                    'tbr': 224,
+                    'asr': 48000,
+                    'vcodec': 'none',
+                    'acodec': 'EC-3',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'audio',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 0,
+                        'height': 0,
+                        'fourcc': 'EC-3',
+                        'language': 'deu',
+                        'codec_private_data': '00063F000000AF87FBA7022DFB42A4D405CD93843BDD0700200F00',
+                        'sampling_rate': 48000,
+                        'channels': 6,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'audio_ext': 'isma',
+                    'video_ext': 'none',
+                    'abr': 224,
+                }, {
+                    'format_id': 'audio_deu-127',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'isma',
+                    'tbr': 127,
+                    'asr': 48000,
+                    'vcodec': 'none',
+                    'acodec': 'AACL',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'audio',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 0,
+                        'height': 0,
+                        'fourcc': 'AACL',
+                        'language': 'deu',
+                        'codec_private_data': '1190',
+                        'sampling_rate': 48000,
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'audio_ext': 'isma',
+                    'video_ext': 'none',
+                    'abr': 127,
+                }, {
+                    'format_id': 'video_deu-23',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 384,
+                    'height': 216,
+                    'tbr': 23,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 384,
+                        'height': 216,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '000000016742C00CDB06077E5C05A808080A00000300020000030009C0C02EE0177CC6300F142AE00000000168CA8DC8',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 23,
+                }, {
+                    'format_id': 'video_deu-403',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 400,
+                    'height': 224,
+                    'tbr': 403,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 400,
+                        'height': 224,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '00000001674D4014E98323B602D4040405000003000100000300320F1429380000000168EAECF2',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 403,
+                }, {
+                    'format_id': 'video_deu-680',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 640,
+                    'height': 360,
+                    'tbr': 680,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 640,
+                        'height': 360,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '00000001674D401EE981405FF2E02D4040405000000300100000030320F162D3800000000168EAECF2',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 680,
+                }, {
+                    'format_id': 'video_deu-1253',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 640,
+                    'height': 360,
+                    'tbr': 1253,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 640,
+                        'height': 360,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '00000001674D401EE981405FF2E02D4040405000000300100000030320F162D3800000000168EAECF2',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 1253,
+                }, {
+                    'format_id': 'video_deu-2121',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 768,
+                    'height': 432,
+                    'tbr': 2121,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 768,
+                        'height': 432,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '00000001674D401EECA0601BD80B50101014000003000400000300C83C58B6580000000168E93B3C80',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 2121,
+                }, {
+                    'format_id': 'video_deu-3275',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 1280,
+                    'height': 720,
+                    'tbr': 3275,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 1280,
+                        'height': 720,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '00000001674D4020ECA02802DD80B501010140000003004000000C83C60C65800000000168E93B3C80',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 3275,
+                }, {
+                    'format_id': 'video_deu-5300',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 1920,
+                    'height': 1080,
+                    'tbr': 5300,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 1920,
+                        'height': 1080,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '00000001674D4028ECA03C0113F2E02D4040405000000300100000030320F18319600000000168E93B3C80',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 5300,
+                }, {
+                    'format_id': 'video_deu-8079',
+                    'url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'manifest_url': 'https://smstr01.dmm.t-online.de/smooth24/smoothstream_m1/streaming/sony/9221438342941275747/636887760842957027/25_km_h-Trailer-9221571562372022953_deu_20_1300k_HD_H_264_ISMV.ism/Manifest',
+                    'ext': 'ismv',
+                    'width': 1920,
+                    'height': 1080,
+                    'tbr': 8079,
+                    'vcodec': 'AVC1',
+                    'acodec': 'none',
+                    'protocol': 'ism',
+                    '_download_params':
+                    {
+                        'stream_type': 'video',
+                        'duration': 370000000,
+                        'timescale': 10000000,
+                        'width': 1920,
+                        'height': 1080,
+                        'fourcc': 'AVC1',
+                        'language': 'deu',
+                        'codec_private_data': '00000001674D4028ECA03C0113F2E02D4040405000000300100000030320F18319600000000168E93B3C80',
+                        'channels': 2,
+                        'bits_per_sample': 16,
+                        'nal_unit_length_field': 4
+                    },
+                    'video_ext': 'ismv',
+                    'audio_ext': 'none',
+                    'vbr': 8079,
+                }],
+                {},
+            ),
         ]
 
         for ism_file, ism_url, expected_formats, expected_subtitles in _TEST_CASES:
diff --git a/test/testdata/ism/ec-3_test.Manifest b/test/testdata/ism/ec-3_test.Manifest
new file mode 100644
index 000000000..45f95de73
--- /dev/null
+++ b/test/testdata/ism/ec-3_test.Manifest
@@ -0,0 +1,1 @@
+<?xml version="1.0" encoding="utf-8"?><!--Transformed by VSMT using XSL stylesheet for rule Identity--><!-- Created with Unified Streaming Platform (version=1.10.12-18737) --><SmoothStreamingMedia MajorVersion="2" MinorVersion="0" TimeScale="10000000" Duration="370000000"><StreamIndex Type="audio" QualityLevels="1" TimeScale="10000000" Language="deu" Name="audio_deu" Chunks="19" Url="QualityLevels({bitrate})/Fragments(audio_deu={start time})?noStreamProfile=1"><QualityLevel Index="0" Bitrate="127802" CodecPrivateData="1190" SamplingRate="48000" Channels="2" BitsPerSample="16" PacketSize="4" AudioTag="255" FourCC="AACL" /><c t="0" d="20053333" /><c d="20053334" /><c d="20053333" /><c d="19840000" /><c d="20053333" /><c d="20053334" /><c d="20053333" /><c d="19840000" /><c d="20053333" /><c d="20053334" /><c d="20053333" /><c d="19840000" /><c d="20053333" /><c d="20053334" /><c d="20053333" /><c d="19840000" /><c d="20053333" /><c d="20053334" /><c d="7253333" /></StreamIndex><StreamIndex Type="audio" QualityLevels="1" TimeScale="10000000" Language="deu" Name="audio_deu_1" Chunks="19" Url="QualityLevels({bitrate})/Fragments(audio_deu_1={start time})?noStreamProfile=1"><QualityLevel Index="0" Bitrate="224000" CodecPrivateData="00063F000000AF87FBA7022DFB42A4D405CD93843BDD0700200F00" FourCCData="0700200F00" SamplingRate="48000" Channels="6" BitsPerSample="16" PacketSize="896" AudioTag="65534" FourCC="EC-3" /><c t="0" d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="20160000" /><c d="19840000" /><c d="8320000" /></StreamIndex><StreamIndex Type="video" QualityLevels="8" TimeScale="10000000" Language="deu" Name="video_deu" Chunks="19" Url="QualityLevels({bitrate})/Fragments(video_deu={start time})?noStreamProfile=1" MaxWidth="1920" MaxHeight="1080" DisplayWidth="1920" DisplayHeight="1080"><QualityLevel Index="0" Bitrate="23909" CodecPrivateData="000000016742C00CDB06077E5C05A808080A00000300020000030009C0C02EE0177CC6300F142AE00000000168CA8DC8" MaxWidth="384" MaxHeight="216" FourCC="AVC1" /><QualityLevel Index="1" Bitrate="403188" CodecPrivateData="00000001674D4014E98323B602D4040405000003000100000300320F1429380000000168EAECF2" MaxWidth="400" MaxHeight="224" FourCC="AVC1" /><QualityLevel Index="2" Bitrate="680365" CodecPrivateData="00000001674D401EE981405FF2E02D4040405000000300100000030320F162D3800000000168EAECF2" MaxWidth="640" MaxHeight="360" FourCC="AVC1" /><QualityLevel Index="3" Bitrate="1253465" CodecPrivateData="00000001674D401EE981405FF2E02D4040405000000300100000030320F162D3800000000168EAECF2" MaxWidth="640" MaxHeight="360" FourCC="AVC1" /><QualityLevel Index="4" Bitrate="2121558" CodecPrivateData="00000001674D401EECA0601BD80B50101014000003000400000300C83C58B6580000000168E93B3C80" MaxWidth="768" MaxHeight="432" FourCC="AVC1" /><QualityLevel Index="5" Bitrate="3275545" CodecPrivateData="00000001674D4020ECA02802DD80B501010140000003004000000C83C60C65800000000168E93B3C80" MaxWidth="1280" MaxHeight="720" FourCC="AVC1" /><QualityLevel Index="6" Bitrate="5300196" CodecPrivateData="00000001674D4028ECA03C0113F2E02D4040405000000300100000030320F18319600000000168E93B3C80" MaxWidth="1920" MaxHeight="1080" FourCC="AVC1" /><QualityLevel Index="7" Bitrate="8079312" CodecPrivateData="00000001674D4028ECA03C0113F2E02D4040405000000300100000030320F18319600000000168E93B3C80" MaxWidth="1920" MaxHeight="1080" FourCC="AVC1" /><c t="0" d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="20000000" /><c d="10000000" /></StreamIndex></SmoothStreamingMedia>
\ No newline at end of file