Search code examples
objective-cvideoafnetworkingsmooth-streamingnsinputstream

Objective-C Download a Smooth Streaming Video


I am wondering if there is a way with Objective-C to download a mp4 video that is setup to stream in a Smooth Streaming format. So far I have tried AFNetworking, NSInputStream, and MPMoviePlayerController to try and access the raw video data, but have come up empty in each try.

I would like to then take this video data and save it as a mp4 to disk to be played offline. The URL looks something like this:

     http://myurl.com/videoname.ism/manifest(format=m3u8-aapl)

Solution

  • I am going to assume that you are asking about an HTTP Live Streaming video, as indicated by your example URL, instead of a Smooth Streaming video. If this is not the case, please leave a comment and I will edit the answer to speak of Smooth Streaming.

    Structure of an HTTP Live Streaming video

    There are multiple versions of HTTP Live Streaming (HLS), the newer of which have added proper support for multilanguage audio and captions, which complicates the scenario significantly. I will assume that you do not have interest in such features and will focus on the simple case.

    HLS has a three-layer structure:

    1. At the root, you have the Master Playlist. This is what the web server provides when you request the video root URL. It contains references to one or more Media Playlists.
    2. A Media Playlist represents the entire video for one particular configuration. For example, if the media is encoded using two quality levels (such as 720p and 1080p), there will be two Media Playlists, one for each. A Media Playlist contains a list of references to the media segments that actually contain the media data.
    3. The media segments are MPEG Transport Streams which contain a piece of the data streams, generally around 10 seconds per file.

    When multilanguage features are out of the picture, it is valid to think of an HLS video as multiple separate videos separated into 10 second chunks - all videos containing the same content but using a different quality level.

    Each of the above entities - Master Playlist, Media Playlist, each media segment - is downloaded separately by a player using standard HTTP file download mechanisms.

    Putting the pieces back together

    All the information a media player requires is present in the media segments - you can mostly ignore the Master Playlist and Media Playlist as their only purpose is to give you the URLs to the media segments.

    Thankfully, the MPEG Transport Stream format is very simple in nature. All you need to do in order to put the media segments back together is to concatenate them together. That's it, really!

    Pseudocode

    I am going to assume that you are not asking about how to perform HTTP requests using Objective-C, as there are many other answers on Stack Overflow on that topic. Instead, I will focus on the algorithm you need to implement.

    First, you simply need to download the Master Playlist.

    masterPlaylist = download(rootUrl);
    

    The Master Playlist contains both comment lines and data lines. Each data line is a reference to a Media Playlist. Note that the lowest quality level for HLS will generally only have the audio stream. Let's assume here you care about whatever the first quality level in the file is, for simplicity's sake.

    masterPlaylistLines = masterPlaylist.split('\n');
    masterPlaylistDataLines = filter(masterPlaylistLines, x => !x.startsWith("#"));
    firstMasterPlaylistDataLine = masterPlaylistDataLines[0];
    

    This data line will contain the relative URL to the Media Playlist. Let's download it. The URL appending code should be smart and understand how to make relative URLs, not simply a string concatenation.

    mediaPlaylist = download(rootUrl + firstMasterPlaylistDataLine);
    

    The Media Playlist, in turn, is formatted the same but contains references to media segments. Let's download them all and append them together.

    mediaPlaylistLines = mediaPlaylist.split('\n');
    mediaPlaylistDataLines = filter(mediaPlaylistLines, x => !x.startsWith("#"));
    
    foreach (dataLine : mediaPlaylistDataLines)
    {
        // URL concatenation code is assumed to be smart, not just string concatenation.
        mediaSegment = download(rootUrl + firstMasterPlaylistDataLine + dataLine);
        appendToFile("Output.ts", mediaSegment);
    }
    

    The final output will be a single MPEG Transport Stream file, playable on most modern media players. You can use various free tools such as FFmpeg if you wish to convert it to another format.