Repository: yt-dlp/yt-dlp

Issue:
[TwitCastingLive] An extractor error has occurred. (caused by KeyError('llfmp4'))
### Checklist

- [X] I'm reporting a broken site
- [X] I've verified that I'm running yt-dlp version **2021.12.27**. ([update instructions](https://github.com/yt-dlp/yt-dlp#update))
- [X] I've checked that all provided URLs are alive and playable in a browser
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [ ] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

_No response_

### Description

Since about a week ago I'm unable to watch live streams of one particular streamer. It worked before but no longer works. Other several streamers that I have tried at random off the front page seem to work just fine.

Streamlink is having the same issue https://github.com/streamlink/streamlink/issues/4281

Looks the JSON response for this streamer's live stream is missing `llfmp4` (what yt-dlp uses) and `fmp4` (what streamlink uses) keys.

### Verbose log

```shell
$ yt-dlp -Uv -o test.ts https://twitcasting.tv/054109
[debug] Command-line config: ['-Uv', '-o', 'test.ts', 'https://twitcasting.tv/054109']
[debug] Encodings: locale UTF-8, fs utf-8, out utf-8, err utf-8, pref UTF-8
[debug] yt-dlp version 2021.12.27 [6223f67a8]
[debug] Python version 3.9.2 (CPython 64bit) - Linux-5.10.0-8-amd64-x86_64-with-glibc2.31
[debug] exe versions: ffmpeg 4.3.2-0, ffprobe 4.3.2-0, rtmpdump 2.4
[debug] Optional libraries: Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
Latest version: 2021.12.27, Current version: 2021.12.27
yt-dlp is up to date (2021.12.27)
[debug] [TwitCastingLive] Extracting URL: https://twitcasting.tv/054109
[TwitCastingLive] Downloading live video of user 054109. Pass "https://twitcasting.tv/054109/show" to download the history
[TwitCastingLive] 054109: Downloading webpage
[debug] [TwitCasting] Extracting URL: https://twitcasting.tv/054109/movie/716611113
[TwitCasting] 716611113: Downloading webpage
[TwitCasting] 716611113: Downloading live info
ERROR: 716611113: An extractor error has occurred. (caused by KeyError('llfmp4')); please report this issue on  https://github.com/yt-dlp/yt-dlp . Make sure you are using the latest version; see  https://github.com/yt-dlp/yt-dlp  on how to update. Be sure to call yt-dlp with the --verbose flag and include its complete output.
Traceback (most recent call last):
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 599, in extract
    ie_result = self._real_extract(url)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/twitcasting.py", line 104, in _real_extract
    for mode, ws_url in stream_server_data['llfmp4']['streams'].items():
KeyError: 'llfmp4'
Traceback (most recent call last):
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 599, in extract
    ie_result = self._real_extract(url)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/twitcasting.py", line 104, in _real_extract
    for mode, ws_url in stream_server_data['llfmp4']['streams'].items():
KeyError: 'llfmp4'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/YoutubeDL.py", line 1353, in wrapper
    return func(self, *args, **kwargs)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/YoutubeDL.py", line 1423, in __extract_info
    ie_result = ie.extract(url)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 629, in extract
    raise ExtractorError('An extractor error has occurred.', cause=e, video_id=self.get_temp_id(url))
yt_dlp.utils.ExtractorError: 716611113: An extractor error has occurred. (caused by KeyError('llfmp4')); please report this issue on  https://github.com/yt-dlp/yt-dlp . Make sure you are using the latest version; see  https://github.com/yt-dlp/yt-dlp  on how to update. Be sure to call yt-dlp with the --verbose flag and include its complete output.
```

[TwitCastingLive] An extractor error has occurred. (caused by KeyError('llfmp4'))
### Checklist

- [X] I'm reporting a broken site
- [X] I've verified that I'm running yt-dlp version **2021.12.27**. ([update instructions](https://github.com/yt-dlp/yt-dlp#update))
- [X] I've checked that all provided URLs are alive and playable in a browser
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [ ] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

_No response_

### Description

Since about a week ago I'm unable to watch live streams of one particular streamer. It worked before but no longer works. Other several streamers that I have tried at random off the front page seem to work just fine.

Streamlink is having the same issue https://github.com/streamlink/streamlink/issues/4281

Looks the JSON response for this streamer's live stream is missing `llfmp4` (what yt-dlp uses) and `fmp4` (what streamlink uses) keys.

### Verbose log

```shell
$ yt-dlp -Uv -o test.ts https://twitcasting.tv/054109
[debug] Command-line config: ['-Uv', '-o', 'test.ts', 'https://twitcasting.tv/054109']
[debug] Encodings: locale UTF-8, fs utf-8, out utf-8, err utf-8, pref UTF-8
[debug] yt-dlp version 2021.12.27 [6223f67a8]
[debug] Python version 3.9.2 (CPython 64bit) - Linux-5.10.0-8-amd64-x86_64-with-glibc2.31
[debug] exe versions: ffmpeg 4.3.2-0, ffprobe 4.3.2-0, rtmpdump 2.4
[debug] Optional libraries: Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
Latest version: 2021.12.27, Current version: 2021.12.27
yt-dlp is up to date (2021.12.27)
[debug] [TwitCastingLive] Extracting URL: https://twitcasting.tv/054109
[TwitCastingLive] Downloading live video of user 054109. Pass "https://twitcasting.tv/054109/show" to download the history
[TwitCastingLive] 054109: Downloading webpage
[debug] [TwitCasting] Extracting URL: https://twitcasting.tv/054109/movie/716611113
[TwitCasting] 716611113: Downloading webpage
[TwitCasting] 716611113: Downloading live info
ERROR: 716611113: An extractor error has occurred. (caused by KeyError('llfmp4')); please report this issue on  https://github.com/yt-dlp/yt-dlp . Make sure you are using the latest version; see  https://github.com/yt-dlp/yt-dlp  on how to update. Be sure to call yt-dlp with the --verbose flag and include its complete output.
Traceback (most recent call last):
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 599, in extract
    ie_result = self._real_extract(url)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/twitcasting.py", line 104, in _real_extract
    for mode, ws_url in stream_server_data['llfmp4']['streams'].items():
KeyError: 'llfmp4'
Traceback (most recent call last):
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 599, in extract
    ie_result = self._real_extract(url)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/twitcasting.py", line 104, in _real_extract
    for mode, ws_url in stream_server_data['llfmp4']['streams'].items():
KeyError: 'llfmp4'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/YoutubeDL.py", line 1353, in wrapper
    return func(self, *args, **kwargs)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/YoutubeDL.py", line 1423, in __extract_info
    ie_result = ie.extract(url)
  File ".pyenv/lib/python3.9/site-packages/yt_dlp/extractor/common.py", line 629, in extract
    raise ExtractorError('An extractor error has occurred.', cause=e, video_id=self.get_temp_id(url))
yt_dlp.utils.ExtractorError: 716611113: An extractor error has occurred. (caused by KeyError('llfmp4')); please report this issue on  https://github.com/yt-dlp/yt-dlp . Make sure you are using the latest version; see  https://github.com/yt-dlp/yt-dlp  on how to update. Be sure to call yt-dlp with the --verbose flag and include its complete output.
```



Discussion:

Resolving patch:
diff --git a/yt_dlp/extractor/twitcasting.py b/yt_dlp/extractor/twitcasting.py
--- a/yt_dlp/extractor/twitcasting.py
+++ b/yt_dlp/extractor/twitcasting.py
@@ -14,6 +14,7 @@
     parse_duration,
     qualities,
     str_to_int,
+    traverse_obj,
     try_get,
     unified_timestamp,
     urlencode_postdata,
@@ -96,12 +97,17 @@ def _real_extract(self, url):
             'Downloading live info', fatal=False)
 
         is_live = 'data-status="online"' in webpage
+
+        if not traverse_obj(stream_server_data, 'llfmp4') and is_live:
+            raise ExtractorError('You must be logged in to watch.', expected=True)
+
         formats = []
         if is_live and not m3u8_url:
             m3u8_url = 'https://twitcasting.tv/%s/metastream.m3u8' % uploader_id
         if is_live and has_websockets and stream_server_data:
             qq = qualities(['base', 'mobilesource', 'main'])
-            for mode, ws_url in stream_server_data['llfmp4']['streams'].items():
+            streams = traverse_obj(stream_server_data, ('llfmp4', 'streams')) or {}
+            for mode, ws_url in streams.items():
                 formats.append({
                     'url': ws_url,
                     'format_id': 'ws-%s' % mode,


Test patch: