Goal: Create a native iOS app using the AVPlayer class and then add simple controls, closed captioning support, and timed metadata observation.
Software Prerequisites:
Knowledge Prerequisites:
Key Steps:
Create a new single view application project in Xcode.
Open Xcode.
From the Welcome to Xcode screen, click Create a new Xcode project.
Select "Single View Application" and then click Next.
From the Choose options for your new project: dialog box, define a product name and an organization identifier.
Setting Description Example Product Name
Identifies the name of the application being created.
iStreamWidgets
Organization Identifier
Defines a unique identifier for your organization. This setting is concatenated with your product name to uniquely identify your application.
com.mycompany.ios
Click Next.
Browse to the location where the project will be stored.
Click Create to create the project.
Define basic project settings, include the AVFoundation framework, and add a view object to the view controller.
Clear the Portrait option. This setting can be found on the Build Settings tab of the project paneThis pane is located in the middle of the window..
From the Identity and Type section of the File Inspector pane, set the Class Prefix option to the desired class prefix (e.g., VLS).
By default, the specified class prefix will be preprended to each new class created in this project. The purpose of this prefix is to avoid name conflicts with classes in external frameworks and libraries.
Add the AVFoundation framework to the project. This framework is required for media playback.
How?
Verify that the root tree item is selected in the Project Navigator paneThis pane is located on the left-hand side of the window..
- From the project paneThis pane is located in the middle of the window., click Build Phases.
- Expand the Link Binary with Libraries section.
- Click the "+" link.
- Find and select "AVFoundation.framework."
- Click Add.
From the Project Navigator paneThis pane is located on the left-hand side of the window., select Main.storyboard.
From the project paneThis pane is located in the middle of the window., expand the View Controller Scene tree and then select View Controller.
From the pane on the right-hand side, view the Attributes pane by clicking .
Under the Simulated Metrics section, set the Size option to "iPhone 5.5-inch."
Under the Simulated Metrics section, set the Orientation option to "Landscape."
Add a view object to the view controller.
How?
- From the project paneThis pane is located in the middle of the window., expand the View Controller branch.
- Drag and drop the View item on to the view controller.
Position the View item to fill the entire controller.
Add a class through which the AVPlayer implementation will be managed.
Add an Objective-C Class to the project.
How?
Press CONTROL and then click the project folderThis folder is selected in the following illustration..
- Select New File.
- Select Cocoa Touch Class.
- Click Next.
- In the Class option, append the term "PlayerView" after the pre-populated class prefix (e.g., VLSPlayerView).
Verify the following settings:
- The Subclass of option should be set to "UIView."
- The Language option should be set to "Objective-C."
- Click Next.
- Verify the location where the class will be located and then click Create.
From the Project Navigator, select VLSPlayerView.h and then update it to match the following code:
// // VLSPlayerView.h // #import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> @interface VLSPlayerView : UIView @property (nonatomic) AVPlayer *player; @end
Select VLSPlayerView.m and then update it to match the following code:
// // VLSPlayerView.m // #import "VLSPlayerView.h" @implementation VLSPlayerView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code } return self; } + (Class)layerClass { return [AVPlayerLayer class]; } - (AVPlayer*)player { return [(AVPlayerLayer *)[self layer] player]; } - (void)setPlayer:(AVPlayer *)player { [(AVPlayerLayer *)[self layer] setPlayer:player]; } /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ @end
A view object was added to the storyboard in step 2. This object needs to be defined as an instance of VLSPlayerView.
From the Project Navigator paneThis pane is located on the left-hand side of the window., select Main.storyboard.
Select the view object nested under the "View" tree item.
View the Identity Inspector pane by clicking from the pane on the right-hand side.
Set the Class option to "VLSPlayerView."
Code the View Controller to play a VLS assetRefers to media (i.e., audio/video content) associated with either your account or a library shared with your account..
From the Project Navigator paneThis pane is located on the left-hand side of the window., select "ViewController.h" and then add the lines indicated in blue font below.
// // ViewController.h // #import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> @class VLSPlayerView; @interface ViewController : UIViewController @property (nonatomic) AVPlayer *player; @property (nonatomic) AVPlayerItem *playerItem; @property (nonatomic, weak) IBOutlet VLSPlayerView *playerView; @end
From the Project Navigator paneThis pane is located on the left-hand side of the window., select "ViewController.m" and then add the lines indicated in blue font below.
// // ViewController.m // #import "ViewController.h" #import "VLSPlayerView.h" @interface ViewController () @end @implementation ViewController @synthesize player, playerView, playerItem; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSURL *content_with_captions = [NSURL URLWithString:@"https://content.uplynk.com/209da4fef4b442f6b8a100d71a9f6a9a.m3u8"]; player = [AVPlayer playerWithURL:content_with_captions]; [self.playerView setPlayer:player]; [player play]; } - (void)didReceiveMemoryWarning{ [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
Replace the playback URL, indicated in orange font below, with one that corresponds to an asset that does not require a signed playback URL.
NSURL *content_with_captions = [NSURL URLWithString:@"https://content.uplynk.com/209da4fef4b442f6b8a100d71a9f6a9a.m3u8"];The asset's Require a token for playback option determines whether a playback URL must be signed.
Set the View Controller to the above view object.
How?
- From the Project Navigator paneThis pane is located on the left-hand side of the window., select Main.storyboard.
- Press CONTROL and then drag the View Controller to the view object positioned on the storyboard.
- From the popup window, select the above view object (i.e., Player View).
The app is now ready to be tested. Upon running the app, the app will load and it should immediately start content playback.
Click to build and run the app.
Verify that the app plays an asset from your library.
AVPlayer provides advanced functionality such as support for closed captions and observing timed metadata. It also lacks a proper set of playback controls, granting you the freedom to create your own playback controls. Let's create a custom play / pause control for use in the app.
Click on MainStoryboard_iPhone.storyboard in the Project Navigator.
Drag a Button object from the Object Library and place it in the bottom middle of your storyboard app. Double click the button text and change it to say Play.
The View Controller, as hub of our app, needs to know 2 things, which button and what happens when it's clicked. Select UTViewController.h and edit it to match the following:
// // UTViewController.h // #import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> @class UTPlayerView; @interface UTViewController : UIViewController @property (nonatomic) AVPlayer *player; @property (nonatomic) AVPlayerItem *playerItem; @property (nonatomic, weak) IBOutlet UTPlayerView *playerView; @property (nonatomic, weak) IBOutlet UIButton *playButton; -(IBAction)play:sender; @end
We've added 1 outlet and 1 action to our app. The outlet tells the view controller which button we want to be our play button. The action is how we tell the view controller what effect we want the play button to have on our app. In our case, it will start playback of content.
Now let's implement the .m changes. Click UTViewController.m and modify it as follows:
// // UTViewController.m // #import <CoreMedia/CoreMedia.h> #import "UTViewController.h" #import "UTPlayerView.h" @interface UTViewController () @end @implementation UTViewController @synthesize player, playerView, playerItem, playButton; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSURL *content_with_captions = [NSURL URLWithString:@"https://content.uplynk.com/209da4fef4b442f6b8a100d71a9f6a9a.m3u8"]; player = [AVPlayer playerWithURL:content_with_captions]; [self.playerView setPlayer:player]; } - (void) play:sender { NSLog(@"play toggled"); if (player.rate == 0.0) { [playButton setTitle:@"Pause" forState:UIControlStateNormal]; [player play]; } else { [playButton setTitle:@"Play" forState:UIControlStateNormal]; [player pause]; } } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
You'll see we've removed the player call to play from our viewDidLoad method. Our video will not automatically start. The play button will be used to start playback. We implemented our play method and added the extra touch of changing its title to Pause during playback, similar to the play button of many multimedia interfaces.
Time to connect the button in Interface Builder. Click on MainStoryboard_iPhone.storyboard. We need to make 2 connections. First control + click and drag from View Controller to the play button and choose playButton from the pop up menu. Next control + click and drag from the play button to the View Controller and choose Play from the pop up menu.
Test the interface and ensure playback starts, pauses and restarts as you click the play button.
You have successfully played content. You can watch it through to the end. It's not very user friendly to force a restart of the application to play the video again, though. Our playerItem provides a notification we can observe and use to "rewind" our video.
In order to implement this observer, we'll modify UTViewController.m again as follows:
// // UTViewController.m // #import <CoreMedia/CoreMedia.h> #import "UTViewController.h" #import "UTPlayerView.h" @interface UTViewController () @end @implementation UTViewController @synthesize player, playerView, playerItem, playButton; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSURL *content_with_captions = [NSURL URLWithString:@"https://content.uplynk.com/209da4fef4b442f6b8a100d71a9f6a9a.m3u8"]; player = [AVPlayer playerWithURL:content_with_captions]; [self.playerView setPlayer:player]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]]; } - (void) play:sender { NSLog(@"play toggled"); if(player.rate == 0.0){ [playButton setTitle:@"Pause" forState:UIControlStateNormal]; [player play]; } else { [playButton setTitle:@"Play" forState:UIControlStateNormal]; [player pause]; } } - (void) playerItemDidReachEnd:(NSNotification *)notification { CMTime zero = CMTimeMakeWithSeconds(0.0, 1); [player seekToTime:zero]; [playButton setTitle:@"Play" forState:UIControlStateNormal]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
We've made 3 changes.
First, we've imported the CoreMedia framework. You'll also need to add it to our list of libraries to link with. Click your root app in the Project Navigator. Click the Build Phases tab. Expand Link Binary With Libraries. Click the + button. Find CoreMedia.framework in the list. Click the Add button.
Second, we've added the playerItemDidReachEnd method that will be called when we observe the end of our content.
Third, we have added an observer to the default center to receive our notification and call our method when we get it. We've given it the name of the method (selector) we want called, playerItemDidReachEnd. Save these changes and test it out. When the video ends, you should be able to replay it with a press of the play button.
One of the advanced benefits of using AVPlayer is its support for closed captions. We'll now add a button to our interface to toggle display of closed captions if our content provides them.
It's a similar drill to adding the play button. We'll start with the storyboard. Click MainStoryboard_iPhone.storyboard, and drag another Round Rect Button from the object library to the interface. Double-click the title and change it to say Captions Off. You may wish to reposition the play button to fit your captions button. Save the storyboard file.
Now let' inform the view controller of the new button. Click on UTViewController.h and make the following modifications:
// // UTViewController.h // #import <UIKit/UIKit.h> #import <AVFoundation/AVFoundation.h> @class UTPlayerView; @interface UTViewController : UIViewController @property (nonatomic) AVPlayer *player; @property (nonatomic) AVPlayerItem *playerItem; @property (nonatomic, weak) IBOutlet UTPlayerView *playerView; @property (nonatomic, weak) IBOutlet UIButton *playButton; @property (nonatomic, weak) IBOutlet UIButton *captionsButton; -(IBAction)play:sender; -(IBAction)toggleCaptions:sender; @end
Next we'll implement our new action, toggleCaptions, in UTViewController.m. Make these modifications:
// // UTViewController.m // #import <CoreMedia/CoreMedia.h> #import "UTViewController.h" #import "UTPlayerView.h" @interface UTViewController () @end @implementation UTViewController @synthesize player, playerView, playerItem, playButton, captionsButton; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSURL *content_with_captions = [NSURL URLWithString:@"https://content.uplynk.com/209da4fef4b442f6b8a100d71a9f6a9a.m3u8"]; player = [AVPlayer playerWithURL:content_with_captions]; [self.playerView setPlayer:player]; [self.player addObserver:self forKeyPath:kTimedMetadataKey options:0 context:TimedMetadataObserverContext]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]]; } - (void) play:sender { NSLog(@"play toggled"); if(player.rate == 0.0){ [playButton setTitle:@"Pause" forState:UIControlStateNormal]; [player play]; } else { [playButton setTitle:@"Play" forState:UIControlStateNormal]; [player pause]; } } - (void) toggleCaptions:sender { NSLog(@"Toggle captions"); player.closedCaptionDisplayEnabled = !player.closedCaptionDisplayEnabled; if(player.closedCaptionDisplayEnabled == NO) [captionsButton setTitle:@"Captions Off" forState:UIControlStateNormal]; else{ [captionsButton setTitle:@"Captions On" forState:UIControlStateNormal]; } } - (void) playerItemDidReachEnd:(NSNotification *)notification { CMTime zero = CMTimeMakeWithSeconds(0.0, 1); [player seekToTime:zero]; [playButton setTitle:@"Play" forState:UIControlStateNormal]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
This is a simple method that toggles display of closed captions, and updates the button's title.
The last thing we need to do is make the outlet and action connections in Interface Builder. Click on MainStoryboard_iPhone.storyboard. Control + click and drag from our View Controller to the captions button. Select captionsButton from the pop up menu. Next control + click and drag from the captions button to the View Controller. Select toggleCaptions from the pop up menu. Save the file.
Naturally you'll need content with closed captions to test this feature. The playback URL used in this tutorial has a few rough captions included.
Timed metadata is included in our streams. Its format is assetID_ray_slicenumber. Ray change observations are useful for determining when the player changes bit rates. Changes in asset ID could indicate your content has entered an ad break and should hide its controls, for example.
We will update our View Controller with an observer and a method that prints the observed data to the debug console.
Click UTViewController.m and edit it to match the following:
// // UTViewController.m // AVTutorialSample // // Created by tbye on 2/20/13. // Copyright (c) 2013 upLynk, LLC. All rights reserved. // #import <CoreMedia/CoreMedia.h> #import "UTViewController.h" #import "UTPlayerView.h" static void *TimedMetadataObserverContext = &TimedMetadataObserverContext; NSString *kTimedMetadataKey = @"currentItem.timedMetadata"; NSArray* tmarray; @interface UTViewController () @end @implementation UTViewController @synthesize player, playerView, playerItem, playButton, captionsButton; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSURL *content_with_captions = [NSURL URLWithString:@"https://content.uplynk.com/209da4fef4b442f6b8a100d71a9f6a9a.m3u8"]; player = [AVPlayer playerWithURL:content_with_captions]; [self.playerView setPlayer:player]; [self.player addObserver:self forKeyPath:kTimedMetadataKey options:0 context:TimedMetadataObserverContext]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]]; } - (void) play:sender { NSLog(@"play toggled"); if(player.rate == 0.0){ [playButton setTitle:@"Pause" forState:UIControlStateNormal]; [player play]; } else { [playButton setTitle:@"Play" forState:UIControlStateNormal]; [player pause]; } } - (void) playerItemDidReachEnd:(NSNotification *)notification { CMTime zero = CMTimeMakeWithSeconds(0.0, 1); [player seekToTime:zero]; [playButton setTitle:@"Play" forState:UIControlStateNormal]; } - (void) toggleCaptions:sender { NSLog(@"Toggle captions"); player.closedCaptionDisplayEnabled = !player.closedCaptionDisplayEnabled; if(player.closedCaptionDisplayEnabled == NO) [captionsButton setTitle:@"Captions Off" forState:UIControlStateNormal]; else{ [captionsButton setTitle:@"Captions On" forState:UIControlStateNormal]; } } - (void)observeValueForKeyPath:(NSString*) path ofObject:(id)object change:(NSDictionary*)change context:(void*)context { if (context == TimedMetadataObserverContext) { tmarray = [[player currentItem] timedMetadata]; for (AVMetadataItem *metadataItem in tmarray) { [self handleTimedMetadata:metadataItem]; } } } - (void)handleTimedMetadata:(AVMetadataItem*)timedMetadata { /* Uplynk metadata comes in the format assetid_ray_slicenum. These values can be observed to help detect bitrate changes or content switches such as ad breaks.*/ NSDictionary *propertyList = (NSDictionary *)[timedMetadata value]; NSLog(@"%@", propertyList); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
The method for accessing timed metadata is an example of Key-Value Observing. In the sample we add a context instance, a variable that keeps track of our key name, and an array to hold our metadata. In our viewDidLoad method we add our key-value observer to our player instance, along with the key we're interested in watching for changes, and the context we declared. When a change in that key occurs, the observeValueForKeyPath method is called. We then compare our context to the context of the change. KVO is used everywhere in iOS. We use the context to make sure we're observing the changes on the objects we expect to be observed. Finally, the handleTimedMetadata method is called when a change is observed. In our case it prints out the timed metadata to our debug console.
You've created the simplest of AVPlayer apps, played our content, created custom controls, and observed timed metadata. From here you can extend the interface to include scrub bars for seeking through content, buttons to share content on Facebook, or integrate with Twitter to allow users to tweet about your content.
For the challenge, try implementing the player view, play and caption buttons on the iPad storyboard, MainStoryboard_iPad.storyboard.