[ie/twitter:broadcast] Support `--wait-for-video`
diff --git a/yt_dlp/extractor/periscope.py b/yt_dlp/extractor/periscope.py
index dcd02192669..3d1375b6450 100644
--- a/yt_dlp/extractor/periscope.py
+++ b/yt_dlp/extractor/periscope.py
@@ -4,6 +4,7 @@
     parse_iso8601,
     unescapeHTML,
 )
+from ..utils.traversal import traverse_obj
 
 
 class PeriscopeBaseIE(InfoExtractor):
@@ -20,8 +21,6 @@ def _parse_broadcast_data(self, broadcast, video_id):
         title = broadcast.get('status') or 'Periscope Broadcast'
         uploader = broadcast.get('user_display_name') or broadcast.get('username')
         title = '%s - %s' % (uploader, title) if uploader else title
-        is_live = broadcast.get('state').lower() == 'running'
-
         thumbnails = [{
             'url': broadcast[image],
         } for image in ('image_url', 'image_url_medium', 'image_url_small') if broadcast.get(image)]
@@ -31,12 +30,16 @@ def _parse_broadcast_data(self, broadcast, video_id):
             'title': title,
             'timestamp': parse_iso8601(broadcast.get('created_at')) or int_or_none(
                 broadcast.get('created_at_ms'), scale=1000),
+            'release_timestamp': int_or_none(broadcast.get('scheduled_start_ms'), scale=1000),
             'uploader': uploader,
             'uploader_id': broadcast.get('user_id') or broadcast.get('username'),
             'thumbnails': thumbnails,
             'view_count': int_or_none(broadcast.get('total_watched')),
             'tags': broadcast.get('tags'),
-            'is_live': is_live,
+            'live_status': {
+                'running': 'is_live',
+                'not_started': 'is_upcoming',
+            }.get(traverse_obj(broadcast, ('state', {str.lower}))) or 'was_live'
         }
 
     @staticmethod
diff --git a/yt_dlp/extractor/twitter.py b/yt_dlp/extractor/twitter.py
index 7bd78eb487e..d7609bc8132 100644
--- a/yt_dlp/extractor/twitter.py
+++ b/yt_dlp/extractor/twitter.py
@@ -1619,6 +1619,9 @@ def _real_extract(self, url):
         info['title'] = broadcast.get('status') or info.get('title')
         info['uploader_id'] = broadcast.get('twitter_username') or info.get('uploader_id')
         info['uploader_url'] = format_field(broadcast, 'twitter_username', 'https://twitter.com/%s', default=None)
+        if info['live_status'] == 'is_upcoming':
+            return info
+
         media_key = broadcast['media_key']
         source = self._call_api(
             f'live_video_stream/status/{media_key}', media_key)['source']

Closes #8473


<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 all of the following options that apply:
- [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*?
- [x] Fix or improvement to an extractor (Make sure to add/update tests)
- [ ] 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))


<!-- Do NOT edit/remove anything below this! -->
</details><details><summary>Copilot Summary</summary>  

<!--
copilot:all
-->
### <samp>🤖 Generated by Copilot at e636a95</samp>

### Summary
🕒🔄🔧

<!--
1.  🕒 - This emoji represents the early return for upcoming videos, as it suggests waiting for the video to start and checking the time.
2.  🔄 - This emoji represents the improved handling of different broadcast states, as it suggests updating or refreshing the video status and adapting to changes.
3.  🔧 - This emoji represents the refactoring and utility function usage, as it suggests fixing or improving the code quality and functionality.
-->
Improved periscope and twitter extractors to handle live broadcasts better. Added `live_status` field to indicate the current state of the video, and skipped downloading videos that are not yet available.

> _Sing, O Muse, of the cunning code review_
> _That made the TwitterBroadcastIE more wise_
> _And taught it to avoid the videos due_
> _That have not yet appeared before the eyes._

### Walkthrough
* Import `traverse_obj` function from `utils.py` to access nested values in a dictionary or a list ([link](https://github.com/yt-dlp/yt-dlp/pull/8475/files?diff=unified&w=0#diff-082a09513b2f03bce9cb19e1d94cd04ee48a8109dbb27ca1f5973502d5c3f4afR5))
* Replace `is_live` field with `live_status` field in `_parse_broadcast_data` function to handle different states of broadcasts ([link](https://github.com/yt-dlp/yt-dlp/pull/8475/files?diff=unified&w=0#diff-082a09513b2f03bce9cb19e1d94cd04ee48a8109dbb27ca1f5973502d5c3f4afL23-L24), [link](https://github.com/yt-dlp/yt-dlp/pull/8475/files?diff=unified&w=0#diff-082a09513b2f03bce9cb19e1d94cd04ee48a8109dbb27ca1f5973502d5c3f4afL38-R42))
* Update `timestamp` and `release_timestamp` fields in `_parse_broadcast_data` function to parse ISO 8601 date string and convert milliseconds to seconds ([link](https://github.com/yt-dlp/yt-dlp/pull/8475/files?diff=unified&w=0#diff-082a09513b2f03bce9cb19e1d94cd04ee48a8109dbb27ca1f5973502d5c3f4afL32-R33))
* Return early from `_real_extract` function in `TwitterBroadcastIE` class if `live_status` is `is_upcoming` to avoid unnecessary requests and errors ([link](https://github.com/yt-dlp/yt-dlp/pull/8475/files?diff=unified&w=0#diff-8b22c3c3c6185164ce8c3e82541f91c728397dec66456eef7e05f664d5729103R1588-R1589))



</details>
