Show Menu
TOPICS×

Implement blackout handling

The TVSDK provides APIs and sample code for handling blackout periods.
To implement blackout handling and provide alternate content during the blackout:
  1. Set up your app to subscribe to blackout tags in a live stream manifest.
 - (void) createMediaPlayer:(PTMediaPlayerItem *)item
 { 
     [PTSDKConfig setSubscribedTags:[NSArray arrayWithObject:<INSERT-BLACKOUT-TAG>]];
     // For example:  
     // [PTSDKConfig setSubscribedTags:[NSArray arrayWithObject:@"#EXT-OATCLS-SCTE35"]];
 }

  1. Add a notification listener for PTTimedMetadataChangedNotification .
    - (void)addobservers 
    { 
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaPlayerSubscribedTagIdentified:)  
          name:PTTimedMetadataChangedNotification object:self.player.currentItem]; 
    }
    
    
  2. Implement a listener method for PTTimedMetadata objects in foreground.
    For example:
    - (void)onMediaPlayerSubscribedTagIdentified:(NSNotification *)notification 
    { 
        NSDictionary *userInfo = [notification userInfo]; 
        PTTimedMetadata *timedMetadata = [(PTTimedMetadata *)[userInfo objectForKey:PTTimedMetadataKey]  
          retain]; 
    
     if ([timedMetadata.name isEqualToString:<INSERT-BLACKOUT-TAG>]) 
        { 
         // handle tag. For example: store it in a dictionary keyed by time to be handled when  
         //   playback time = timedMetadata time. 
            NSNumber *timedMetadataStartTime =  
              [NSNumber numberWithInt:(int)CMTimeGetSeconds(timedMetadata.time)]; 
            [timedMetadataCollection setObject:timedMetadata forKey:timedMetadataStartTime]; 
        } 
    
        [timedMetadata release]; 
    }
    
    
  3. Handle TimedMetadata objects with constant updates during playback.
    - (void)onMediaPlayerTimeChange:(NSNotification *)notification 
    { 
        @synchronized(self) 
        { 
            CMTimeRange seekableRange = self.player.seekableRange; 
            if (CMTIMERANGE_IS_VALID(seekableRange)) 
            { 
                _currentTime = (int) CMTimeGetSeconds(self.player.currentTime); 
                if (isnan(_currentTime)) 
                { 
                    _currentTime = 0; 
                } 
                [self handleCollectionAtTime:_currentTime]; 
            } 
        } 
    }
    
    
  4. Add the PTTimedMetadata handler to switch to alternate content and return to main content as indicated by the PTTimedMetadata object and its playback time.
    - (void)handleCollectionAtTime:(int)currentTime 
    { 
        NSArray *allKeys = nil; 
        NSMutableArray * timedMetadatasToDelete = [[[NSMutableArray alloc]init]autorelease]; 
    
        if (!_inBlackout && timedMetadataCollection) 
        { 
            allKeys = [timedMetadataCollection allKeys]; 
            int count = [allKeys count]; 
            for (int i=count-1; i>-1; i--) 
            { 
                NSNumber *currTimedMetadataTime = allKeys[i]; 
                PTTimedMetadata *currTimedMetadata =  
                  [timedMetadataCollection objectForKey:currTimedMetadataTime]; 
    
                if (currentTime == [currTimedMetadataTime integerValue] &&  
                                   currTimedMetadata.name == <INSERT-BLACKOUT-TAG> &&  
                                   [self isBlackoutStart: currTimedMetadata]) 
                { 
                                    PTAdMetadata *newItemAdMetadata =  
                                      [self createAlternateMediaMetadata];            
    
                // 1. Turn off preroll on the alternate media item. 
                    newItemAdMetadata.enableLivePreroll = NO; 
    
                                PTMediaPlayerItem *newItem =  
                                  [[PTMediaPlayerItem alloc]initWithUrl: 
                                    <INSERT-ALTERNATE-STREAM-URL mediaId:<INSERT-ALTERNATE-STREAM- 
                                     MEDIA-ID> metadata:newItemAdMetadata];
    
               // 2. Register the current (original playback item) in background. 
                    [self.player registerCurrentItemAsBackgroundItem]; 
    
               // 3. Replace the current playback item with the alternate stream. 
                     [self.player replaceCurrentItemWithPlayerItem:newItem]; 
    
               // 4. Reset observers. 
                    [self removeObservers]; 
                    [self addobservers]; 
    
               // 5. Register listener on the subscribed tags in background item. 
                    [[NSNotificationCenter defaultCenter] addObserver:self  
                       selector:@selector(onSubscribedTagInBackground:)  
                        name:PTTimedMetadataChangedInBackgroundNotification  
                          object:self.player.currentItem]; 
    
               // 6. Register listener on the error in background item. 
                             [[NSNotificationCenter defaultCenter]  
                                addObserver:self selector:@selector(onBackgroundManifestError:)  
                                name:PTBackgroundManifestErrorNotification   
                                  object:self.player.currentItem]; 
    
               // 7. Resume playback 
                         [self.player play]; 
    
                        // 8. Set boolean to true to handle blackout end. 
                      _inBlackout = YES; 
                      break; 
                } 
            } 
        } 
        else if (_inBlackout && backgroundTimedMetadataCollection) 
        { 
            allKeys = [backgroundTimedMetadataCollection allKeys]; 
            int count = [allKeys count]; 
            for (int i=count-1; i>-1; i--) 
            { 
                NSNumber *currTimedMetadataTime = allKeys[i]; 
                PTTimedMetadata *currTimedMetadata =  
                  [backgroundTimedMetadataCollection objectForKey:allKeys[i]]; 
    
                if (currentTime == ([currTimedMetadataTime integerValue] &&  
                  currTimedMetadata.name == <INSERT-BLACKOUT-TAG>  &&  
                  [self isBlackoutEnd:currTimedMetadata] ) 
                {      
                                   // 1. Come out of blackout. Unregister background item. 
                               [self.player unregisterCurrenBackgroundItem]; 
    
                        PTMetadata *metadata = [self createMetadata]; 
                        PTAdMetadata *adMetadata =  
                          (PTAdMetadata *)[currMetadata metadataForKey:PTAdResolvingMetadataKey]; 
                              adMetadata.enableLivePreroll = NO; 
    
                                PTMediaPlayerItem *item =  
                                  [[[PTMediaPlayerItem alloc] initWithUrl:<INSERT-ORIGINAL-URL>  
                                    mediaId:<INSERT-ORIGINAL-MEDIAID> metadata:metadata autorelease]; 
    
                                    // 2. Switch back to original item. 
                        [self.player replaceCurrentItemWithPlayerItem:item]; 
                        self.player.autoPlay = YES; 
                        [self removeObservers]; 
    
                        // 3. Remove background item listener. 
                        [[NSNotificationCenter defaultCenter] removeObserver:self  
                           name:PTTimedMetadataChangedInBackgroundNotification  
                        object:self.player.currentItem]; 
    
                                   [[NSNotificationCenter defaultCenter] removeObserver:self  
                                      name:PTBackgroundManifestErrorNotification 
                        object:self.player.currentItem]; 
                        [self addobservers]; 
                        [self.player play]; 
    
                                // 4. Update boolean to correctly maintain the current state. 
                        _inBlackout = NO; 
                        break; 
                } 
            } 
        } 
    }
    
    
  5. Implement a listener method for PTTimedMetadata objects in the background.
    - (void)onSubscribedTagInBackground:(NSNotification *)notification 
    { 
        NSDictionary *userInfo = [notification userInfo]; 
        PTTimedMetadata *timedMetadata = [(PTTimedMetadata *) 
          [userInfo objectForKey:PTTimedMetadataKey] retain]; 
    
        if ([timedMetadata.name isEqualToString:<INSERT-BLACKOUT-TAG>]) 
        { 
            NSNumber *timedMetadataStartTime =  
              [NSNumber numberWithInt:(int)CMTimeGetSeconds(timedMetadata.time)]; 
            [backgroundTimedMetadataCollection  
               setObject:timedMetadata forKey:timedMetadataStartTime]; 
        } 
    
        [timedMetadata release]; 
    }
    
    
  6. Implement a listener method for background errors.
    - (void) onBackgroundManifestError:(NSNotification *)notification 
    { 
        NSLog (@"onBackgroundManifestError"); 
    }
    
    
  7. If the blackout range is on the DVR in the playback stream, update the non-seekable ranges.
    // This sample assumes that blackoutStartTimedMetadata is the PTTimedMetadata  
    // object that indicated "blackout start", and assuming blackoutEndTimedMetadata is the  
    // PTTimedMetadataObject that indicated "blackout end". Since in this case they are both  
    // in DVR, both are notified to the application before playback starts. This is the right  
    // time for the application to set this range in DVR as non-seekable range. 
    
    CMTime ignoreRangeStart = blackoutStartTimedMetadata.time; 
    CMTime ignoreRangeDuration = CMTimeMakeWithSeconds(CMTimeMakeWithSeconds  
      (CMTimeGetSeconds(blackoutEndTimedMetadata.time) -   
         CMTimeGetSeconds(blackoutStartTimedMetadata.time)),  
           blackoutEndTimedMetadata.time.timescale); 
    
    CMTimeRange ignoreRange = CMTimeRangeMake(ignoreRangeStart, ignoreRangeDuration); 
    NSArray *ignoreRangeArray = [NSArray arrayWithObject:[NSValue valueWithCMTimeRange:ignoreRange]]; 
    PTBlackoutMetadata *blackoutMetadata =  
      [[PTBlackoutMetadata alloc]initWithNonSeekableRanges:ignoreRangeArray]; 
    PTMetadata *currMetadata = self.item.metadata; 
    
    if (currMetadata) 
    { 
        [currMetadata setMetadata:blackoutMetadata forKey:PTBlackoutMetadataKey] 
    }