AVPlayer - Retrieve audio tracks in all formats - ios

I'm working on a tvOS application where I'm using the AVPlayer to play an HLS playlist which provides audio in two formats for some languages. For example:
French (AAC)
French (EC-3)
English
I'm trying to display a custom dialog that would allow the users to select between each of these tracks.
The playlist looks like this:
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-mp4a.40.2",NAME="Français",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="fr",URI="..."
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-ec-3",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,LANGUAGE="fr",URI="..."
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-mp4a.40.2",NAME="English",DEFAULT=NO,AUTOSELECT=YES,LANGUAGE="en",URI="..."
The problem is that, from what I can tell, the AVPlayer groups the tracks by language and it never returns all the 3 tracks.
(lldb) po player?.currentItem?.asset.mediaSelectionGroup(forMediaCharacteristic: .audible)
▿ Optional<AVMediaSelectionGroup>
- some : <AVAssetMediaSelectionGroup: 0x283961940, options = (
"<AVMediaSelectionKeyValueOption: 0x2839a5a00, language = fr, mediaType = 'soun', title = Français, default = YES>",
"<AVMediaSelectionKeyValueOption: 0x2839a5b00, language = en, mediaType = 'soun', title = English>"
), allowsEmptySelection = YES>
I went deeper into the French item (player?.currentItem?.asset.mediaSelectionGroup(forMediaCharacteristic: .audible)?.options.first) but I still couldn't find anything useful. I also tried looking at other fields from the AVPlayer with no success.
Even when I use the AVPlayerViewController I only see two audio tracks to choose from.
Is there any way to get all the available audio tracks?

So the issue here is actually the playlist. If you check the HLS specification, there are some notes explaining this under the Rendition Groups subsection of EXT-X-MEDIA (https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-07#section-4.4.6.1.1)
A Playlist MAY contain multiple Groups of the same TYPE in order to
provide multiple encodings of that media type. If it does so, each
Group of the same TYPE MUST have the same set of members, and each
corresponding member MUST have identical attributes with the
exception of the URI and CHANNELS attributes.
Generally, the way to think about it is, anything within a given GROUP-ID is selectable by the user (and so AVFoundation reveals it to you). But which GROUP-ID is playing is selected by the player, and (for this scenario) this is determined by the AUDIO attribute of the EXT-X-STREAM-INF that the player has selected.
If you want the surround audio track to be selectable, then it needs to exist in the same GROUP-ID as the rest of the AUDIO tracks. If you don't have control of the manifest, you can test this by re-writing the GROUP-ID of the surround French track (using something like Charles Proxy), from audio-ec-3 to audio-mp4a.40.2; it should appear in AVFoundation after that. But a word of warning, for the HLS to remain valid, the CODECS attribute of all of the EXT-X-STREAM-INF tags have to be updated to include the CODECS defined by the surround track (otherwise playback failure may occur).
If you want to leave it up to the player to select, and you do not have a surround English track, you still have to give the English option in the surround group to remain valid HLS, but you can just leave the URI identical to the one defined in the stereo group. Again CODECS will have to be updated in this scenario.
This video from WWDC gives a good explanation of all of this (relevant section starts around 42:39): https://developer.apple.com/videos/play/wwdc2018/502/

Related

Tracking down URLs of deleted videos in a youtube playlist

I've recently made a Python program using the Youtube api v3 where, given a playlist id, it fetches certain information from every video in the playlist. However, through both the output of this code and this post on Google, it's pretty clear that information on videos that were either privated or deleted is not available through the Youtube api.
Is there an alternative program or resource that I can use to extract information from these unavailable videos, in particular their video ids?
The only solution I can think of now is to access the HTML of the display Youtube and search through it for certain strings (like "[deleted video]") and to then extract the id corresponding to that string. But, I've never dealt with HTML and, if I understand HTML correctly, I'd have to load a new page for every 50 videos in the playlist, which for playlists with thousands of videos, becomes rather inefficient and laborious.
I was hoping to use something like PyTube, but that couldn't handle unavailable videos either.
Edit
Here is the code that extracts the video ids:
from googleapiclient.discovery import build
api_key = "AI~~~" #get from yt (private key)
yt = build("youtube", "v3", developerKey = api_key)
plst_id = "PLorw3mfu-J0ipF4Ss0XgR8IxcwP-JzNKC" #unique yt playlist id
plst_req = yt.playlistItems().list( #request for info from yt api
part = "contentDetails",
playlistId = plst_id,
maxResults = 50
)
plst = plst_req.execute()
vid_ids = [] #available video ids taken from current playlist
for vid in plst['items']:
vid_ids.append(vid['contentDetails']['videoId'])
print(vid_ids)
print(plst['pageInfo']['totalResults'])
The first line printed contains the video ids of every available video in the playlist. The second line printed gives the number of videos in the playlist, including available and unavailable ones.
The playlist used in the code above is given here. It contains 10 total videos, of which one of them is unavailable.
In this case, the output is (with a valid api key)
['bv_cEeDlop0', 'mRKTOZmX2cE', '5ACvKdx1nns', 'wSNhP8b_Avo', 's56cHgokPlE', 'E4IHMWnQiMw', 'sCDkPShADSc', 'EVwgeUVVDYU', 'Z8Mqw0b9ADs']
10
Youtube still treats unavailable videos as an element of the playlist, but does not give out it's video id. In this particular instance, the video id of the unavailable video is "t83zUmjr05I", which is not hard to find manually: copy the link address of the deleted video and extract the part after the "v=".
But, on a larger scale manual extraction becomes tedious.
Here's a permanent fix to that!
You can try tube_dl.
pip install tube_dl
It uses modular approach and is up to date
More about this can be found at : https://github.com/shekharchander/tube_dl/
Maybe, the playlist module can help you with that. It uses regex to grab all video IDs not JSON. Please let me know if the problem is fixed or not, I will update module accordingly.
Edit
Here's the working code
from tube_dl import Playlist
p = Playlist('https://youtube.com/playlist?list=PLorw3mfu-J0ipF4Ss0XgR8IxcwP-JzNKC').videos
print(p)

AVCaptureSession audio doesn't work for long videos

I'm using AVCaptureSession to record a video with audio. Everything seems to work properly for short videos, but for some reason, if I record a video that is longer than about 12 seconds, the audio doesn't work.
Edit (because this answer is still getting upvotes): This answer works to mitigate the problem but the likely root cause for the issue is addressed in #jfeldman's answer.
I found the solution as an answer to a completely different question.
The issue is the movieFragmentInterval property in AVCaptureMovieFileOutput.
The documentation for this property explains what these fragments are:
A QuickTime movie is comprised of media samples and a sample table
identifying their location in the file. A movie file without a sample
table is unreadable.
In a processed file, the sample table typically appears at the
beginning of the file. It may also appear at the end of the file, in
which case the header contains a pointer to the sample table at the
end. When a new movie file is being recorded, it is not possible to
write the sample table since the size of the file is not yet known.
Instead, the table is must be written when recording is complete. If
no other action is taken, this means that if the recording does not
complete successfully (for example, in the event of a crash), the file
data is unusable (because there is no sample table). By periodically
inserting “movie fragments” into the movie file, the sample table can
be built up incrementally. This means that if the file is not written
completely, the movie file is still usable (up to the point where the
last fragment was written).
It also says:
The default is 10 seconds. Set to kCMTimeInvalid to disable movie
fragment writing (not typically recommended).
So for some reason my recording is getting messed up whenever a fragment is written. I just added the line movieFileOutput.movieFragmentInterval = kCMTimeInvalid; (where movieFileOutput is the AVCaptureMovieFileOutput I've added to the AVCaptureSession) to disable fragment writing, and the audio now works.
We also experienced this issue. Basically disabling movie fragment writing will work but it doesn't actually explain the issue. Most likely you are recording to an output file using a file extension that does not support this feature, like mp4. If you pass an output file with the extension mov you should have no issues using movie fragment writing and the output file will have audio.
Updating videoFileOutput.movieFragmentInterval = kCMTimeInvalid solved this for me.
However, I accidentally set the movieFragmentInterval after calling startRecordingToOutputFileURL. An agonizing hour later I realized my mistake. For newbies like me, note this obvious sequence.
videoFileOutput.movieFragmentInterval = kCMTimeInvalid
videoFileOutput.startRecordingToOutputFileURL(filePath, recordingDelegate: recordingDelegate)
kCMTimeInvalid is now deprecated. This is how to assign it now:
videoFileOutput?.movieFragmentInterval = CMTime.invalid

YouTube channel and playlist ID prefixes

I've noticed normal channel IDs have "UC" at the start while automatically generated ones have "HC". Normal playlists have "PL" but special ones, like uploaded or favourite videos, have "UU" and "FL", which however doesn't apply to autogenerated channels, they have "LP". That's what I've found so far. Is there any list of all these prefixes with their meaning?
Edit: Playlists based on a specific video begin with "RD"+the video ID.
There is not a guideline and they may always change. I strongly suggest not to depend on them but the pure API methods.

How to use AVFoundation to extract (or 'demux') subtitle from mp4 video?

I am trying to create a small procedure that will take an mp4 video and extract the subtitle information from the video using AVFoundation framework. In doing so it will create and return an NSArray of NSDictionary elements in the format startTimeOfSubtitle, endTimeofSubtitle and subtitleString.
This is what I gathered from a release note for AVFoundation. Any code example would be much appreciated.
Thanks in advance.
Selection of audio and subtitle media according to language and other criteria
AVFoundation now offers features for the discovery of options that may be offered by audiovisual media resources to accommodate differing language preferences, accessibility requirements, custom application configurations, and other needs, and for selection of these options for playback. For example, a resource may contain multiple audible options, each with dialog spoken in a different language, to be selected for playback to the exclusion of the others. Similar options in multiple languages can also be provided for legible media, such as subtitles. Both file-based content and HTTP Live Streaming content can offer media options. To obtain information about the groups of options that are offered by an instance of AVAsset:
•
Load the value of the AVAsset key availableMediaCharacteristicsWithMediaSelectionOptions using AVAsynchronousKeyValueLoading. When loading is complete, -[AVAsset availableMediaCharacteristicsWithMediaSelectionOptions] will provide an NSArray that may contain AVMediaCharacteristicAudible, AVMediaCharacteristicLegible, or AVMediaCharacteristicVisual, or any combination of these, to indicate the availability of groups of mutually exclusive options.
•
Each group of mutually exclusive options with a media characteristic of interest can be obtained via -[AVAsset mediaSelectionGroupForMediaCharacteristic:]. To obtain the audible options, pass AVMediaCharacteristicAudible, etc. Each group is represented by an instance of AVMediaSelectionGroup. Each option within a group is represented by an instance of AVMediaSelectionOption. Both of these classes are defined in AVMediaSelectionGroup.h.
To examine available options within a group and to filter them for selection for playback:
•
AVMediaSelectionGroup offers methods in the category AVMediaSelectionOptionFiltering that perform common filtering operations on arrays of AVMediaSelectionOptions, according to whether the options are playable, match a desired locale, or either have or do not have special media characteristics, such as whether they offer specific features for accessibility. Media characteristics that indicate the presence of accessibility features, which can be used to filter media selection options, have been defined in AVMediaFormat.h.
•
AVMediaSelectionOption offers information about options that may be used for display in a user interface that allows users to select among available options or in the implementation of client-defined filtering operations. As an example of client-defined filtering option in an application that makes use of custom media resources, options may be considered eligible for selection only if their associated metadata contains a specific value.
•
To select a specific option within a group for playback, use -[AVPlayerItem selectMediaOption:inMediaSelectionGroup:]. To discover the option that's currently selected for playback, use -[AVPlayerItem selectedMediaOptionInMediaSelectionGroup:].
Advice about subtitles
Special care should be taken when displaying options to the user among the available legible options for playback and when making a selection among the available legible options according to user preferences. Some legible content contains "forced" subtitles, meaning that according to the content author's intent the subtitles should be displayed when the user has neither indicated a preference for the display of subtitles nor made an explicit selection of a subtitle option. Forced subtitles are typically used in order to convey the meaning of spoken dialog or visible text in a language that the content provider assumes will not be commonly understood, when comprehension of the dialog or text is nevertheless considered to be essential. Be sure that your app allows them to be displayed appropriately by following the advice below.
An AVMediaSelectionGroup for the characteristic AVMediaCharacteristicLegible can provide two types of legible options: 1) for display of legible content that's considered to be elective along with content that's considered to be essential, and 2) for display of essential legible content only. Legible AVMediaSelectionOptions that include essential content only have the media characteristic AVMediaCharacteristicContainsOnlyForcedSubtitles (defined in AVMediaFormat.h). When offering legible options for display to the end user in a selection interface, or when considering subtitle options for automatic selection according to a user preference for language, legible options with the characteristic AVMediaCharacteristicContainsOnlyForcedSubtitles should be excluded. +[AVMediaSelectionOption mediaSelectionOptionsFromArray:withoutMediaCharacteristics:], specifying AVMediaCharacteristicContainsOnlyForcedSubtitles as a characteristic to exclude, can be used to obtain the legible options that are suitable to offer to the end user in a selection interface or for consideration for selection according to a user preference.
If the user indicates no preference for or makes no selection of legible content, the application should select one of the legible options for playback that has the characteristic AVMediaCharacteristicContainsOnlyForcedSubtitles, if any are present. For most resources containing legible options with forced-only subtitles, an appropriate selection among them can be made in accordance with the current audible selection. Use -[AVMediaSelectionOption associatedMediaSelectionOptionInMediaSelectionGroup:] to obtain the legible option associated with an audible option. If there is no other means available to choose among them, the first legible option with forced-only subtitles in the media selection group is an appropriate default.
Well Iam not sure if you would be able to get start time and end time of subtitles... Iam using following method to get information about subtitle option in HLS stream.
[tmpCurrentAsset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:#"tracks"] completionHandler:^{
dispatch_async(queueForMultipleAudioHandling, ^(void) {
AVKeyValueStatus postLoadingStatus = [tmpCurrentAsset statusOfValueForKey:#"tracks" error:NULL];
if (postLoadingStatus == AVKeyValueStatusLoaded)
{
AVMediaSelectionGroup * subtitleGroup = [self.mPlayer.currentItem.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
if (subtitleGroup) {
_subtitles = subtitleGroup;
dispatch_async(dispatch_get_main_queue(), ^{
[this createSubtitlePopOver];
});
}
}
First you need to reload tracks and get information about AVMediaCharacteristicLegible. If you have this, than you can extract information about subtitles like this: _subtitles.options This will gave you array of AVMediaSelectionOption from which you can choose. If you want to play more with AVMediaSelectionOption, you can continue reading here: AVMediaSelectionOption documentation
Hope this was a little bit helpful ;)

YouTube JS API: Detect "embedding has been disabled"

Using the youtube javascript api (http://code.google.com/apis/youtube/js_api_reference.html), I am trying to allow a user to embed a video into some content he creates in my app. I have gotten everything working, except for being able to detect and handle the case that embedding is not allowed for the video.
Currently, the player loads and shows a thumbnail of the disallowed video, and it only gives an error once the user tries to play it. This is bad because the user may not play the video before saving / sending his content. I would like to preemptively detect that the video is not allowed to be embedded, and display a helpful message to the user.
The only solution I can see is to actually play it (programmatically) and handle the error that is raised at that point.
Existing workaround:
Embed player (embedSWF)
onYouTubePlayerReady(): add onError onStateChange event listeners.
onStateChange(newState): when the video finishes loading, try to play it.
e.g. if (newState == 5 /* CUED */) { player.mute(); player.playVideo(); player.stopVideo(); player.unMute(); }
onError(error): if the video failed to play in onStateChange, will receive error here.
Is there a better way?
Thanks!
-Rob
You can grab the JSON feed for that video entry before you embed it and see if "yt$format":5 exists, which is the embed SWF. It won't be there if embedding is disabled.
http://gdata.youtube.com/feeds/api/videos/video_id?v=2&alt=json-in-script
I think that you might be looking for the videoSyndicated or the videoEmbeddable parameter. The API documentation says:
videoEmbeddable
The videoEmbeddable parameter lets you to restrict a search to only videos that can be embedded into a webpage. If you specify a value for this parameter, you must also set the type parameter's value to video.
Reference: https://developers.google.com/youtube/v3/docs/search/list#videoEmbeddable
videoSyndicated
The videoSyndicated parameter lets you to restrict a search to only videos that can be played outside youtube.com. If you specify a value for this parameter, you must also set the type parameter's value to video.
Reference: https://developers.google.com/youtube/v3/docs/search/list#videoSyndicated
Example Call
With both:
GET https://www.googleapis.com/youtube/v3/search?&part=snippet,statistics&videoSyndicated=true&videoEmbeddable=true&key=${yourKey}
I know this isn't directly to the question, but in case someone using PHP stumbles upon this problem, there's a method getNoEmbed() in a Zend_Gdata_YouTube_VideoEntry.
Taken from the docs:
If the return value is an instance of Zend_Gdata_YouTube_Extension_NoEmbed, this video cannot be embedded.

Resources