How to Add External .Vtt Subtitle File to Avplayerviewcontroller in Tvos

How to add external .vtt subtitle file to AVPlayerViewController in tvOS

While it was almost mentioned no where in the apple documentation, I read in an article that subtitles need to be embedded in the HLS stream.

Subtitles are not intended to be added manually.. though I did find a StackOverflow post showing a hack. Unsure if it works or not.

Seeing that I use VIMEO Pro, the HLS stream that is provided has the WebVTT subtitles (that I uploaded to vimeo) embedded, thus solving my problem.

tvOS: Anyway to display a subtitle outside of the AVPlayer?

So I figured it out! It basically makes use a combination of the Media Accessibility API, which allows you to get the values the user has chosen for their captions/subtitle settings, Attributed Strings, and a subclass UILabel (although this could maybe be substituted with a UITextView as that will allow you to set it's UIEdgeInsets natively)

So, first, the subclass is to allow the UILabel to be inset. This is because captions can have a background color AND a text highlight color and without the inset, the text highlight is all you see. So the function the subclass is simple:

class InsetUILabel: UILabel {
override func drawTextInRect(rect: CGRect) {
let inset: CGFloat = 15
let insets: UIEdgeInsets = UIEdgeInsets(top: inset, left: inset/2, bottom: inset, right: inset/2)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
}

And for generating the actual label. This uses a label called textSample, but you can obviously make it a little more general.

import MediaAccessibility

func styleLabel(sampleText: String) {
let domain = MACaptionAppearanceDomain.User

// Background styling
let backgroundColor = UIColor(CGColor: MACaptionAppearanceCopyWindowColor(domain, nil).takeRetainedValue())
let backgroundOpacity = MACaptionAppearanceGetWindowOpacity(domain, nil)
textSample.layer.backgroundColor = backgroundColor.colorWithAlphaComponent(backgroundOpacity).CGColor
textSample.layer.cornerRadius = MACaptionAppearanceGetWindowRoundedCornerRadius(domain, nil)

// Text styling
var textAttributes = [String:AnyObject]()
let fontDescriptor = MACaptionAppearanceCopyFontDescriptorForStyle(domain, nil, MACaptionAppearanceFontStyle.Default).takeRetainedValue()
let fontName = CTFontDescriptorCopyAttribute(fontDescriptor, "NSFontNameAttribute") as! String
let fontColor = UIColor(CGColor: MACaptionAppearanceCopyForegroundColor(domain, nil).takeRetainedValue())
let fontOpacity = MACaptionAppearanceGetForegroundOpacity(domain, nil)
let textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(domain, nil)
let textHighlightColor = UIColor(CGColor: MACaptionAppearanceCopyBackgroundColor(domain, nil).takeRetainedValue())
let textHighlightOpacity = MACaptionAppearanceGetBackgroundOpacity(domain, nil)
let textEdgeShadow = NSShadow()
textEdgeShadow.shadowColor = UIColor.blackColor()
let shortShadowOffset: CGFloat = 1.5
let shadowOffset: CGFloat = 3.5

switch(textEdgeStyle) {
case .None:
textEdgeShadow.shadowColor = UIColor.clearColor()

case .DropShadow:
textEdgeShadow.shadowOffset = CGSize(width: -shortShadowOffset, height: shortShadowOffset)
textEdgeShadow.shadowBlurRadius = 6

case .Raised:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: shadowOffset)
textEdgeShadow.shadowBlurRadius = 5

case .Depressed:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: -shadowOffset)
textEdgeShadow.shadowBlurRadius = 5

case .Uniform:
textEdgeShadow.shadowColor = UIColor.clearColor()
textAttributes[NSStrokeColorAttributeName] = UIColor.blackColor()
textAttributes[NSStrokeWidthAttributeName] = -2.0

default:
break
}

textAttributes[NSFontAttributeName] = UIFont(name: fontName, size: (textSample.font?.pointSize)!)
textAttributes[NSForegroundColorAttributeName] = fontColor.colorWithAlphaComponent(fontOpacity)
textAttributes[NSShadowAttributeName] = textEdgeShadow
textAttributes[NSBackgroundColorAttributeName] = textHighlightColor.colorWithAlphaComponent(textHighlightOpacity)

textSample.attributedText = NSAttributedString(string: sampleText, attributes: textAttributes)
}

Now the text highlight section makes use of shadows, with values I think look pretty good, but you might want to tweak them a tiny bit. Hope this helps!

AVUrlAsset and WebVTTs

I've posted a solution over here: https://stackoverflow.com/a/37945178/171933 Basically you need to use an AVMutableComposition to join the video with the subtitles and then play back that composition.

How to add Navigation Markers as Chapters for my video in tvOS?


Introduction

To be able to add chapters the concept isn't as complicated as I had originally thought. You'll need to use the following main classes:

  • AVMetadataItem
  • AVMutableMetadataItem
  • AVTimedMetadataGroup
  • AVNavigationMarkersGroup

Part 1: Creating a chapter generating method

To simplify the work, we'll create a method that generates a "chapter" or as you'll see a AVTimedMetadataGroup that consists of a title, a description, and a start time.

The main issue I had was that in Swift2 we could specify the identifier type directly with AVMetadataItem (EDIT: This is factually wrong, in their examples they created a custom class called AVMettadataItem that used AVMutableMetadataItem to generate what they wanted) which isn't possible anymore with Swift3. However, this can be solved with AVMutableMetadataItem

For example this is how we create a AVMutableMetadataItem for the title of the chapter:

let titleItem =  AVMutableMetadataItem()
titleItem.identifier = AVMetadataCommonIdentifierTitle
titleItem.value = title as (NSCopying & NSObjectProtocol)?

Each of these items needs to be added to a list that we'll call "items" ( [AVMetadataItem] )

Once your list is completed, you'll want to create the AVTimedMetadataGroup with the list you just created and the timeRange that will be applied to your video (aka: Where you chapter will start and how long it lasts)

In my case, chapter duration isn't a big deal so I set it to a fixed time (10 seconds).

The thing that you need to watch out for is, if the CMTime is not valid, the chapter does not show up in the list.

private func setupNavigationMarker(title: String, description: String, timeStart: Int64)-> AVTimedMetadataGroup {

let timeRange = CMTimeRange(start: CMTimeMake(timeStart, 1), duration: CMTimeMake(10, 1))

var items: [AVMetadataItem] = []
let titleItem = AVMutableMetadataItem()
titleItem.identifier = AVMetadataCommonIdentifierTitle
titleItem.value = title as (NSCopying & NSObjectProtocol)?
items.append(titleItem)

let descriptionItem = AVMutableMetadataItem()
descriptionItem.identifier = AVMetadataCommonIdentifierDescription
descriptionItem.value = description as (NSCopying & NSObjectProtocol)?
items.append(descriptionItem)

return AVTimedMetadataGroup(items: items, timeRange: timeRange)

}

Part 2: Add your chapter to your AVNavigationMarkerGroup

Then somewhere in your AVPlayerViewController (like ViewDidLoad):

var timedMetadataGroupList = [AVTimedMetadataGroup]()

timedMetadataGroupList.append(self.setupNavigationMarker(title: "test1", description: "description test1" , timeStart: 100))
timedMetadataGroupList.append(self.setupNavigationMarker(title: "test2", description: "description test2" , timeStart: 200))
timedMetadataGroupList.append(self.setupNavigationMarker(title: "test3", description: "description test3" , timeStart: 300))

let navigationMarkersGroup = AVNavigationMarkersGroup(title: "Chapters", timedNavigationMarkers: timedMetadataGroupList)

avPlayerItem.navigationMarkerGroups.append(navigationMarkersGroup)

Part 3: Add images to your chapters

Coming soon...

https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/UsingAVKitPlatformFeatures/UsingAVKitPlatformFeatures.html

tvOS - how to retrieve objects from class in topshelf ServiceProvider.swift?

When you want to share files between targets (iphone/ipad/tv/watch/other services), click on the file in Xcode, you on the right part of the screen, you will see a "target membership" option. Select whichever target you want your code to be accessible to.

Now that I have answered my own question, my advice: DO NOT retrieve data to your topshelf the way I was going about it. There is a predefined way of getting data into topshelf. Just download the code sample from Apple here and look at the code: https://developer.apple.com/library/content/samplecode/UICatalogFortvOS/Introduction/Intro.html



Related Topics



Leave a reply



Submit