Vilynx Swift SDK Guide v1.3

Swift SDK 1.3 changelog

New methods for managing memory and resources

/**
Cancels all downloads and release some resources.
*/
public func releaseResources()

/**
Clears all the loopers.
*/
public func clearLoopers()

/**
Clears all the loopers except the last one. Use it if you want to free up resources, but don't want to stop the current video.
*/
public func clearLoopersExceptLast()

Also the loopers array of the SDK is public now.

About AVPlayerLayers built by the SDK

All the AVPlayerLayers that are build using the SDK now are named "VilynxVideo", you you can iterate over a view sublayer's to find it. An example of searching for the Vilynx video layer:

// Check that the view has sublayers
if let sublayers = UIView.layer.sublayers {
    // Iterate through the sublayers
    for layer in sublayers {
        // Find the layers named VilynxVideo
        if layer.name == "VilynxVideo" {
            // Cast it to AVPlayerLayer, pause the video, release player and remove layer
            let playerLayer = layer as! AVPlayerLayer
            playerLayer.player?.pause()
            playerLayer.player = nil
            layer.removeFromSuperlayer()
        }
    }
}

Changes on the UIView extension

A new method named clearVilynxSublayers() was added to the UIView extension, that will look at all the view sublayers and remove the ones created by the Vilynx SDK. This make managing the resources easier.

A new method named getVilynxPlayerLayer() was added to retrieve the player layer from a view if it exists (if not it returns nil).

Changes on VilynxRequest

The parameters videoQuality and imageQuality are now optionals.

getVilynxResponses() now has an extra parameter named frame (optional too):

func getVilynxResponses(requests: [VilynxRequest], frame: CGRect? = nil, completion: @escaping ([VilynxResponse]) -> ())

If you pass the frame parameter, then the video and image quality will be handled by the SDK.

You need to set the frame or the request image and video quality or the SDK will assert you.

How to implement the Vilynx SDK in a iOS/tvOS Xcode project

1. Adding the VilynxSDK to your Xcode project

Manually adding the SDK

To add the VilynxSDK to your Xcode project:

  • Add the file VilynxSDK.framework to your Xcode project.
  • From the General tab of your project add VilynxSDK.framework to Embedded Binaries and Linked Framework and Libraries.
  • Import VilynxSDK from your source files.

Now you need to set your Vilynx public key. From your AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    Vilynx.sharedInstance.configureWith(publicKey: "YOUR_PUBLIC_KEY")
    return true
}

Adding the SDK using Carthage

If you have access to the Vilynx Swift SDK GitHub repo, you can use Carthage to include the SDK. Just add the repo to your Cartfile:

git "git@github.com:Vilynx/swift_gallery.git" ~> 1.3

And run on your porjects root directory:

carthage update

A new folder named Carthage will be created. Inside it you will have the iOS and tvOS built frameworks. Just drag them to your project. Before submitting your app, please read the section related to it below.

2. Making requests with the SDK

For making requests using the SDK, you need to create VilynxRequest objects and call Vilynx.sharedInstance.getVilynxResponses

var requests = [
    VilynxRequest(videoId: "vid-1",
        title: "Ad Paco Rabanne - Million Crazy Dance",
        subtitle: nil,
        videoUrl: nil,
        videoQuality: VilynxVideoQuality.VIDEO_PRO69_MID_QUALITY,
        imageQuality: VilynxImageQuality.IMAGE_PRO69_HIGH_QUALITY),
    VilynxRequest(videoId: "vid-2",
        title: "Economic Impact of Immigrants",
        subtitle: nil,
        videoUrl: nil,
        videoQuality: VilynxVideoQuality.VIDEO_PRO69_MID_QUALITY,
        imageQuality: VilynxImageQuality.IMAGE_PRO69_HIGH_QUALITY),
    VilynxRequest(videoId: "vid-3",
        title: "Fjallraven Surf",
        subtitle: nil,
        videoUrl: nil,
        videoQuality: VilynxVideoQuality.VIDEO_PRO69_MID_QUALITY,
        imageQuality: VilynxImageQuality.IMAGE_PRO69_HIGH_QUALITY),
    VilynxRequest(videoId: "vid-4",
        title: "Mike Tittel BTS - Tennis",
        subtitle: nil,
        videoUrl: nil,
        videoQuality: VilynxVideoQuality.VIDEO_PRO69_MID_QUALITY,
        imageQuality: VilynxImageQuality.IMAGE_PRO69_HIGH_QUALITY),
    VilynxRequest(videoId: "vid-5",
        title: "MTV Video Music Awards 2016",
        subtitle: nil,
        videoUrl: nil,
        videoQuality: VilynxVideoQuality.VIDEO_PRO69_MID_QUALITY,
        imageQuality: VilynxImageQuality.IMAGE_PRO69_HIGH_QUALITY)
]

Vilynx.sharedInstance.getVilynxResponses(requests: self.requests) { [weak self] responses in
    self?.responses = responses
    completion()
}

The only required parameters for the constructor of VilynxRequest are videoId, videoQuality and imageQuality. The quality parameters specify the resolution of the resource. title, subtitle and videoUrl are used to build the VilynxResponse objects that are returned on the completion clousure of getVilynxResponses.

VilynxReponse comes with this variables:

var videoUrl: String?
var thumbnailUrl: String?
var videoPreviewUrl: String?
var id: String!
var title: String?
var subtitle: String?

3. Requesting a video preview

Once you have all your response objects, you can use them to build a video player and thumbnail image. The SDK comes with integrated cache for video resources.

For building players, the SDK has these functions:

/**
- url: url of the resource to load on the player
- frame: frame of the requested player
- Returns: an AVPLayerLayer configured to loop.
*/
public func getLoopingPlayerLayer(url: String, frame: CGRect) -> AVPlayerLayer


/**
- url: url of the resource to load on the player
- frame: frame of the requested player
- Returns: an AVPlayerLayer
*/
public func getPlayerLayer(url: String, frame: CGRect) -> AVPlayerLayer


/**
- url: url of the resource to load
- Returns: an AVPlayerItem with the resource from the url
*/
public func getPlayerItem(url: String) -> AVPlayerItem

All the player layers created by the SDK are named VilynxVideo, so you can search for them later by iterating on a view sublayers:

if let sublayers = UIView.layer.sublayers {
    for layer in sublayers {
        if layer.name == "VilynxVideo" {
            let playerLayer = layer as! AVPlayerLayer
            playerLayer.player?.pause()
            playerLayer.player = nil
            layer.removeFromSuperlayer()
        }
    }
}

4. Using the UIView extension to directly build a player

The SDK also comes with an UIView extension, that allows you to add a preview directly to any UIView subclass (UIButton, UIImage...).

public func addVilynxVideoLayer(url: String)

You can access to the player that got added to the view by the property .vilynxPlayer which also comes with the extension.

The AVPlayerLayer can be also accessed by calling .getVilynxPlayerLayer().

Since all the player layers that are built by the SDK are named VilynxVideo, the extension also comes with a function named clearVilynxSublayers() that will clear all the player layers that are on a view.

The player that is loaded comes with an associated looper, so you do not have to worry about configuring it. But since the loopers need to have an strong reference to them, the SDK will keep an array of all the loopers that are created. You can call Vilynx.sharedInstance.clearLoopers() release all the loopers except the last one.

The EmbedViewController that comes with the example app uses this method by calling it on a UIButton button.addVilynxVideoLayer(url: urlVideo).

5. Preloading a video

Sometimes you would like to start loading some video resources before the user requests them, to avoid waiting times. The SDK comes with a function for doing it:

/**
Starts downloading a resource and cache it.
- Parameter url: url of the resource to preload
*/
public func preloadResource(url: String)

6. Video and image quality enums

The SDK has two predefined structs to represent the quality of the video and thumbnail images that you want to request. Those enums are:

public enum VilynxVideoQuality: String {
    case VIDEO_PRO69_HIGH_QUALITY = "pro69high.viwindow.mp4"
    case VIDEO_PRO69_MID_QUALITY = "pro69.viwindow.mp4"
    case VIDEO_PRO69_GALLERY_QUALITY = "gallery.pro69.viwindow.mp4"
    case VIDEO_SHORT_QUALITY = "short.viwindow.mp4"
    case VIDEO_SHORT_GALLERY_QUALITY = "gallery.short.viwindow.mp4"
}

public enum VilynxImageQuality: String {
    case IMAGE_PRO69_HIGH_QUALITY = "pro69high.viwindow0.jpg"
    case IMAGE_PRO69_MID_QUALITY = "pro69.viwindow0.jpg"
    case IMAGE_PRO69_GALLERY_QUALITY = "gallery.pro69.viwindow0.jpg"
    case IMAGE_SHORT_QUALITY = "short.viwindow0.jpg"
    case IMAGE_SHORT_GALLERY_QUALITY = "gallery.short.viwindow0.jpg"
}

They are used on videoQuality and imageQuality when you build the VilynxRequest objects.

7. Managing the memory and releasing loopers and players

iOS and tvOS have a hard limit of 16 AVPlayer objects that can be instantiated at the same time. If you try to create more players after that thay will not work. Because of that, you have to be careful with the players that you have at each time and release them when they are no longer needed.

When you call Vilynx.sharedInstance.getLoopingPlayerLayer(url: String, frame: CGRect) -> AVPlayerLayer or use the UIView extension the Vilynx SDK will keep a strong reference to the looper, and the player will not be completly cleared as long as this references is alive. To releasing loopers and resources the SDK comes with 2 methods:

  • clearLoopers(): Will release all the loopers except the last one created. This is useful if you are making some kind of fade-in animation of the thumbnail image, since deleting all the loopers would stop the player from playing before the animation is completed.
  • releaseResources(): will clear all the loopers, useful when you want to completely stop all the players. It is recommended to call this function on viewWillDisappear ViewController life-cycle method to release the resources.

Additionally when a player layer is no longer needed it needs to be removed from it's super layer, and it's player to get assigned to nil so no references are keept:

playerLayer.removeFromSuperlayer()
playerLayer.player = nil

If you have only one player on your ViewController, you need to do that everytime you want to change the video to play, so you would need to keep a reference to current AVPlayerLayer, remove and release it, request a new one from the SDK and add it.

If you have multiple views where the player gets embed (like the embed example of the demo of tvOS), then you would need to keep a reference to the previous focused view, assign a name to the AVPlayerLayer to identify it (or keep a reference), and when switching focus, remove the layer from the previous view and add a new player to the current focused view:

class EmbedViewController: UIViewController, DataPreload {
    fileprivate var prevButton: UIButton? = nil

    fileprivate var responses: [VilynxResponse]!

    @IBOutlet var embedScrollview: UIScrollView!

    override func viewDidDisappear(_ animated: Bool) {
        Vilynx.sharedInstance.releaseResources()
    }

    override func viewDidLoad() {
        // Fill the scroll view...
    }

    //This is called every time that the focus is in a button
    override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        ...
        previewVideo(button: button, index: button.tag)
        ...
    }

    func previewVideo(button: UIButton, index: Int) {
        let urlVideo = responses[index].videoPreviewUrl!
        guard URL(string: urlVideo) != nil else {
            return
        }

        // If a button was previously focused
        if let pb = prevButton {
            var videoLayer: AVPlayerLayer? = nil

            // First check that the view has sublayers, it would crash if not
            if let sublayers = pb.layer.sublayers {
                for sublayer in pb.layer.sublayers! {
                    // When using the UIView extension, the layer added is named "VilynxVideo"
                    // Find the sublayer that corresponds to the player layer
                    if sublayer.name == "VilynxVideo" {
                        videoLayer = sublayer as? AVPlayerLayer
                        // Pause the current playing video
                        videoLayer?.player?.pause()
                    }
                }
            }

            // Start hiding the button, the thumbnail image is under it
            let anim = UIViewPropertyAnimator(duration: 0.3, curve: .linear) {
                pb.alpha = 0.01
            }

            // When animation is completed, set the button alpha back to 1.0, remove the videoLayer from its superView and clear the loopers.
            anim.addCompletion { (_) in
                pb.alpha = 1.0
                videoLayer?.player? = nil
                videoLayer?.removeFromSuperlayer()
                Vilynx.sharedInstance.releaseResources()
            }
            anim.startAnimation()
        }

        // Reassign the reference to the previous button, now it would be the current focused one
        prevButton = button
        // Use the UIView extension to add the player
        button.addVilynxVideoLayer(url: urlVideo)
    }
}

8. Working with scrollable views

When working with scrollable views, requesting the player layer each time the focus of the view gets changed will end up making a laggy scroll with frame drops. To avoid that, instead of requesting each video, you should use a DispatchWorkItem, that triggers after the focus has not changed for an amount of time (0.15 seconds will work great). While the focus is changing you should cancel the previous DispatchWorkItem, and create another one for the new focused element:

class EmbedViewController: UIViewController {
    ...
    fileprivate var task: DispatchWorkItem?
    ...
    func previewVideo(button: UIButton, index: Int) {
        // If a task exists, cancel it
        if let task = task {
            task.cancel()
        }

        ...
        // Create a new task, where the player layer will be requested
        task = DispatchWorkItem {
            ...
            button.addVilynxVideoLayer(url: urlVideo)
        }
        ...
        // Task will be executed only after 0.15 seconds, if the focus changed it will cancel
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.15, execute: task!)
    }
}

9. Important! before submitting your app

The VilynxSDK.framework comes as a fat binary which includes code for both the simulator (i386/x86_64) and physical device (ARM). Before submitting your app to the App Store you must trim the simulator binary code from your build. There are multiple options for doing it.

a. Using Carthage

If you got Carthage installed just add a new Run Script to your project (Project > Target > Build Phases > '+' button > New Run Script Phase).

Copy the pre-compiled framework to {Project's root directory}/Carthage/Build/iOS or {Project's root directory}/Carthage/Build/tvOS.

Add the following line to the script area:

/usr/local/bin/carthage copy-frameworks

To the Input Files add:

$(SRCROOT)/Carthage/Build/iOS/VilynxSDK.framework

If you are targeting tvOS, replace iOS from the path to tvOS.

To the Output Files add:

$(DERIVED_FILE_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Vilynx.framework

b. Using a script

You can also create an script which uses lipo for trimming the x86 code. Create a new file at your projects root directory and name it 'trim.sh' with the following code and give execution permissions to the file:

FRAMEWORK=$1
echo "Trimming $FRAMEWORK..."
FRAMEWORK_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/$FRAMEWORK.framework/$FRAMEWORK"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
echo "Extracting $ARCH..."
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done
echo "Merging binaries..."
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
echo "Done."

Create a new directory at your project's root and name it 'Frameworks'. Copy the VilynxSDK inside the new directory.

Now you need to run the script from Xcode each time it compiles. Create a new Run Phase (Project > Target > Build Phases > '+' button > New Run Script Phase) and add this line of code inside the script area:

${SRCROOT}/trim.sh VilynxSDK

10. Additional information

If you need to know when the video or the thumbnail is completely loaded you can add and observer.

playerLayerTemp.player?.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
thumbnail.addObserver(self, forKeyPath: "image", options: NSKeyValueObservingOptions(rawValue: 0), context: nil)

Once the observer is created, you can trigger any action you need as follows

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (object is AVPlayer && keyPath == "status") {
        //An action
    }
    if (object is UIImageView && keyPath == "image") {
        //An action
    }
}

When looping videos keep a strong reference to the AVPlayerLooper that you create (i.e. in an array at class level). Otherwise if the looper gets released the player will stop looping.

Download SDK + Example

To get access to our SDKs, please contact us at info@vilynx.com.