Customer Sign In

upLynk

upLynk Content in iOS AppsUsing AVPlayer (Documentation)

Tutorial thumbnail

Summary Play upLynk content in your own native iOS App.

Skill level required Understanding of Objective-C and iOS SDK

Time to complete 1-2 hours

Things you’ll need
  • An iOS Development Environment
  • An Apple Developer Account if you wish to test on your iOS device
  • An asset in your upLynk CMS account with DRM tokens required turned off. See Step 4. of Embedding the upLynk Player, Part 2 for instructions on disabling the token requirement.

Introduction

This tutorial will simply and quickly get your upLynk content playing back in a native iOS app using the AVPlayer class(Documentation). We will then expand on the tutorial by adding simple controls, adding support for closed captions, and adding code to observe timed metadata.

Step 1. Setting up the Project

Fire up Xcode. On the File menu click New and select Project. Select the "Single View Application" click the Next button.

On the "Choose options for your new project:" pane, give your project a Product Name. Also provide a Class Prefix. The Class Prefix will be used as the prefix to any classes created for your project. The tutorial uses the Class Prefix "UT", but you are free to choose what you'd like. Double check Devices has Universal selected, Use Storyboards is checked and Use Automatic Reference Counting is also checked. Click the Next button.

Select a location on disk to save your project and click Create.

Step 2. The Simple Player

In the Project Navigator, find and click the file MainStoryboard_iPhone.storyboard.

In the View Controller Scene tree, click View Controller. Now on the other side of Xcode find the attributes inspector. Under Simulated Metrics find Status Bar and set it to None.

Now add a View object to our scene by dragging it from the library onto our application's view controller's view. Position it to fill the entire screen.

Step 3. Our Player Class

We need to add a new class to our project that will manage our AVPlayer implementation. In the Project Navigator find the root project folder. Note this is different from the project root. We want the folder. Control + click this folder and select New File. Select Objective-C Class and click the Next button. You'll notice the class name prompt includes your Class Prefix. Add the name PlayerView to the Class Prefix and click the Next button. The default location should be your project root folder. Click the Create button.

Click your new UTPlayerView.h file in your Project Navigator. Edit it to match the following:

UTPlayerView.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//
//  UTPlayerView.h
//

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface UTPlayerView : UIView

@property (nonatomic) AVPlayer *player;

@end

Now select UTPlayerView.m and edit it to match the following:

UTPlayerView.m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
//  UTPlayerView.m
//

#import "UTPlayerView.h"

@implementation UTPlayerView

- (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
		

You may have noticed we included AVFoundation.h in our UTPlayerView.h file. We'll need to add that framework to our project to avoid link errors. In the Project Navigator click the root of the entire tree. It is your project's name with the blue Xcode app icon next to it. In the middle pane click Build Phases. Expand the item titled Link Binary with Libraries. Click the + button. Scroll till you find AVFoundation.framework, select it, and click the Add button.

Step 4. Connecting Our PlayerView

We previously added a View Object to our Storyboard. We need to tell that View that it is an instance of our UTPlayerView.

In the Project Navigator, click once again on MainStoryboard_iPhone.storyboard.

click the iPhone storyboard once to select our view. Double check in the View Controller Scene tree that you have the view, nested in a view, nested in the view controller selected.

Next, in the right hand column, click to show the Identity Inspector. Under Custom Class it currently says UIView. Change it to say UTPlayerView (It should auto-complete for you if you've created the class and added it to your project as we did in Step 3).

While we're here, click on the File Inspector for the UTPlayerView. Under Interface Builder Document, de-select Use Autolayout. This will prevent a possible build issue we might see later on.

Step 5. Setup the View Controller

The View Controller is the hub of our app. Sticking with our goal of a simple player, we're going to hard wire our upLynk Playback URL into the View Controller to initially play content. First, we need to tell our View Controller about our PlayerView so it knows where to display the video. Click and select your View Controller's .h file in the Project Navigator. The tutorial's view controller is called UTViewController.h. Edit it to match the following:

UTViewController.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//
//  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;
@end

Next edit UTViewController.m to match the following:

UTViewController.m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//
//  UTViewController.m
//

#import <CoreMedia/CoreMedia.h>
#import "UTViewController.h"
#import "UTPlayerView.h"

@interface UTViewController ()

@end

@implementation UTViewController
@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:@"http://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

You may want to replace the URL in this sample code with your own playback URL copied from the upLynk CMS. Ensure the asset you choose does not require a DRM token to play.

Now that the View Controller knows that we want to play the upLynk content after it loads, we need to tell it, via our storyboard, which PlayerView we mean exactly. Click once again on MainStoryboard_iPhone.storyboard. Under View Controller Scene control+click and drag the interface builder connection arrow from the view controller to our PlayerView Object on the storyboard. When you release the button you'll see a popup with options. Select PlayerView. Save this change.

Step 6. Test Playback

We're now ready to test. Make sure you've selected iPhone 6.1 Simulator for your test device and click the Run button. We expect the app to load and then immediately play content.

Step 7. Playback Controls

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//
//  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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//
//  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:@"http://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.

Step 8. Rewind

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//
//  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:@"http://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.

Step 9. Closed Captions

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 upLynk 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//
//  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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//
//  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:@"http://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.

Step 10. Observing Stream Embedded Timed Metadata

Timed metadata is included in upLynk 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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
//
//  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:@"http://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 (Documentation). 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.

Finished! Your Next Challenge

You've created the simplest of AVPlayer apps, played upLynk 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.

Good luck!

Back to tutorials