googlecast / CastVideos-ios

Reference iOS Sender w/ Framework API: CastVideos-ios application shows how to cast videos from an iOS device that is fully compliant with the Cast Design Checklist.
Apache License 2.0
195 stars 63 forks source link

Can't cast vtt link from opensubtitles #32

Open ghost opened 6 years ago

ghost commented 6 years ago

I have the vtt subtitles: http://dl.opensubtitles.org/en/download/subformat-vtt/filead/src-api/vrf-19e20c62/sid-3DjBbbDAOmE0miVvn9bn508EQRf/1954851996 But I can't cast to chromecast Please help me!

wwe-johndpope commented 6 years ago

this may or may not help.

public struct WWEVideo: Unboxable {
    /*
     "video_id": 1765,
     "video_id_mlbam": 0,
     "video_id_vms": 0,
     "video_id_media_playback": 0,
     "video_premium": false,
     "video_show": {
     "show_name": "Raw",
     "show_color": "235,24,38,1",
     "show_id": 1092
     },
     "video_season_id": 0,
     "video_rating": "TV-PG",
     "video_eventdate": "1/30/17",
     "video_duration": 210,
     "video_number_of_views": 319644991,
     "video_type": "video",
     "video_title": "Samoa Joe and Shinsuke Nakamura lay it all on the line for the NXT Championship in Osaka, Japan: WWE NXT Live Event, Dec. 3, 2016",
     "video_description": "1000"
     "video_thumbnail_image_name": "http://www.wwe.com/f/2016/12/007_007_nxt_12022016rf_3786_6c38bafebbd967e565f8eff0a9ee23f1--2c4adf3224d7a8423e831ba6dddabf4b.jpg",
     "video_stream_url": "https://vod.wwe.com/vod/2016/,1080,720,540,432,360,288,/nxtosaka_joe_nakamura_matchclip.m3u8",
     "video_stream_url_cc":"https:\/\/cdn1.wwe.com\/hd_video1\/wwe\/2017\/top30_sslam_moments_081217\/top30_sslam_moments_081217_cc_en.vtt",
     "video_stream_url_mp4":"https:\/\/cdn1.wwe.com\/hd_video1\/wwe\/2017\/top30_sslam_moments_081217\/top30_sslam_moments_081217_640x360.mp4"

     */

    var id: Int
    var mID: String
    var vms: Int
    var mediaPlayback: Int
    var premium: Bool // optional
    var show: WWEShow? // optional
    var seasonId: Int
    var rating: String
    var eventDate: String // WARNING - this is not return in associated content
    var duration: Int
    var viewCount: Int
    var videoType: String
    var title: String
    var videoDescription: String
    var thumbURL: String
    var streamingURL: String
    var streamingCC: String
    var videoCount: Int

    func url() -> URL {
        //print("streamingURL = \(streamingURL)")
        return URL(string: streamingURL)!
    }

    var playlistItems: [WWEVideo]?

    init() {
        id = 0
        mID = ""
        vms = 0
        mediaPlayback = -1
        premium = false
        show = nil
        seasonId = -1
        videoCount = -1
        rating = ""
        eventDate = ""
        duration = 100
        viewCount = 0
        videoType = ""
        title = ""
        videoDescription = ""
        thumbURL = ""
        streamingURL = ""
        streamingCC = ""
        playlistItems = []
    }

   public init(unboxer: Unboxer) throws {
        do {
            id = try unboxer.unbox(key: "video_id")
            mID = try unboxer.unbox(key: "video_id_mlbam")
            vms = try unboxer.unbox(key: "video_id_vms")

            // optional
            do {
                mediaPlayback = try unboxer.unbox(key: "video_id_media_playback")
            }catch{
                mediaPlayback = -1
            }

            do {
                premium = try unboxer.unbox(key: "video_premium")
            }catch{
                premium = false
            }

            if let _ = unboxer.dictionary["video_show"] {
                show = try? unbox(dictionary: unboxer.dictionary, atKey: "video_show")
            } else {
                show = nil
            }

            do {
                seasonId = try unboxer.unbox(key: "video_season_id")
            } catch {
                seasonId = -1 //to prevent quiting if this is null
            }

            do {
                videoCount = try unboxer.unbox(key: "video_count")
            } catch {
                videoCount = -1 //to prevent quiting if this is null
            }

            do {
                rating = try unboxer.unbox(key: "video_rating")
            } catch {
                rating = "" //to prevent quiting if this is null
            }

            do {
                let strDate: String = try unboxer.unbox(key: "video_eventdate")

                if let date: Date = DateFormatters.mdyFormatter.date(from: strDate) {
                    eventDate = DateFormatters.mdySpaceFormatter.string(from: date)
                }else{
                    eventDate = ""
                }
            } catch {
                eventDate = "" //to prevent quiting if this is null
            }

            duration = try unboxer.unbox(key: "video_duration")
            viewCount = try unboxer.unbox(key: "video_number_of_views")
            videoType = try unboxer.unbox(key: "video_type")

            do {
                title = try unboxer.unbox(key: "video_title")
            } catch {
                title = "" //to prevent quiting if this is null
            }

            do {
                videoDescription = try unboxer.unbox(key: "video_description")
            } catch {
                videoDescription = "" //to prevent quiting if this is null
            }

            thumbURL = try unboxer.unbox(key: "video_thumbnail_image_name")

             do {
                streamingURL = try unboxer.unbox(key: "video_stream_url")
             }catch{
                streamingURL = ""
            }

            do {
                streamingCC = try unboxer.unbox(key: "video_stream_url_cc")
            }catch{
                streamingCC = ""
            }

            if let _ = unboxer.dictionary["video_playlist_items"] {
                playlistItems = try unbox(dictionary: unboxer.dictionary, atKey: "video_playlist_items")
            } else {
                playlistItems = []
            }
        }
    }

    func isPremium() -> Bool {
        return premium
    }
}
//
//  ChromeCastPlayer.swift
//  iOSChromecastSampleApp
//

import Foundation
import CoreMedia
import GoogleCast
import AVFoundation
import CocoaLumberjack

let kSubsIdentifier : Int = 99

class ChromeCastPlayer: NSObject {

    var state: BasicPlayerState
    var stateChange: Event<(old: BasicPlayerState, new: BasicPlayerState)>
    var currentTime: CMTime
    var currentTimeChange: Event<CMTime>
    var duration: CMTime?
    var curationChange: Event<CMTime?>
    var currentBitrate: Double?
    var currentBitrateChange: Event<Double?>
    var maxBitrate: Double?
    var maxResolution: CGSize?

    let enableSDKLogging = true

    override init()  {
        state = .null
        stateChange = Event()
        currentTime = CMTime.init()
        currentTimeChange = Event()
        curationChange = Event()
        currentBitrateChange = Event()

        GoogleCastLockControls.shared.setup()
        super.init()

    }

    func getCastView( color:UIColor = .black) -> UIView {
        let button = GCKUICastButton.init()
        button.tintColor = color
        return button
    }

    func openUrl(  video: WWEVideo,  contentType: String = "application/x-mpegurl",  streamType: BasicPlayerStreamType = .vod, addToQueue:Bool = false,
                   callback:(Bool) -> Void
        ) {

        if  let session =  GCKCastContext.sharedInstance().sessionManager.currentCastSession,
            let remoteMediaClient = session.remoteMediaClient {

            let metadata = GCKMediaMetadata(metadataType: .generic)
            metadata.setString("Title", forKey: video.title)
            metadata.setString("Subtitle", forKey: video.eventDate)
            metadata.addImage(GCKImage(url: URL.init(string: video.thumbURL)! , width: 164, height: 82))

            var duration : Double = Double(video.duration)
            if (streamType == .live) { duration = TimeInterval.infinity }

            var _streamType: GCKMediaStreamType = .buffered
            if (streamType == .live) { _streamType = .live }

            //Subtitle text style:
            let textTrackStyle : GCKMediaTextTrackStyle = GCKMediaTextTrackStyle.createDefault()
            textTrackStyle.backgroundColor = GCKColor(uiColor: .clear)

            if let mediaInfo = video.mediaInfo(){
                let requestOptions = GCKMediaLoadOptions()
                if addToQueue{
                    requestOptions.autoplay = true
                }

                requestOptions.playPosition = 0

                if video.hasSubTitles() && EZPlayerSettings.shared.closeCaptioning { //User's settings -> CC/subs enabled.
                    requestOptions.activeTrackIDs = [NSNumber(integerLiteral: kSubsIdentifier)]
                }

                let request = remoteMediaClient.loadMedia(mediaInfo, with: requestOptions)
                request.delegate = ChromeCastManager.shared
                remoteMediaClient.add(ChromeCastManager.shared)

                GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
            }else{
                    DDLogError("🔥 FATAL: couldn't retrieve media info from video")
            }
            callback(true)

        } else {
            callback(false)
        }
    }

    func play() {
        if  let session =  GCKCastContext.sharedInstance().sessionManager.currentCastSession,
            let remoteMediaClient = session.remoteMediaClient {
            remoteMediaClient.play()
        }
    }

    func pause() {
        if  let session =  GCKCastContext.sharedInstance().sessionManager.currentCastSession,
            let remoteMediaClient = session.remoteMediaClient {
            remoteMediaClient.pause()
        }
    }

    func seek(to: CMTime, callback: ((Bool) -> Void)?) {
        if  let session =  GCKCastContext.sharedInstance().sessionManager.currentCastSession,
            let remoteMediaClient = session.remoteMediaClient {

            let seekOptions = GCKMediaSeekOptions()
            seekOptions.interval = to.seconds
            seekOptions.relative = false
            seekOptions.resumeState = .play

            remoteMediaClient.seek(with: seekOptions)
        }
    }

}

// MARK: - GCKLoggerDelegate
extension ChromeCastPlayer: GCKLoggerDelegate {
    func logMessage(_ message: String, fromFunction function: String) {
        if enableSDKLogging {
            // Send SDK's log messages directly to the console.
            print("\(function)  \(message)")
        }
    }

}

// Chrome specific WWE video extension
extension WWEVideo{

    func hasSubTitles()->Bool{
        let videoHasSubs : Bool = !self.streamingCC.isEmpty && (self.streamingCC.lowercased().range(of: "http") != nil)
        return videoHasSubs
    }

    func mediaInfo()-> GCKMediaInformation?{

        let streamType:BasicPlayerStreamType = .vod;
        let metadata = GCKMediaMetadata(metadataType: .generic)
        metadata.setString("Title", forKey: self.title)
        metadata.setString("Subtitle", forKey: self.eventDate)
        metadata.addImage(GCKImage(url: URL.init(string: self.thumbURL)! , width: 164, height: 82))

        var duration : Double = Double(self.duration)
        if (streamType == .live) { duration = TimeInterval.infinity }

        var _streamType: GCKMediaStreamType = .buffered
        if (streamType == .live) { _streamType = .live }

        func __getMediaTracks() -> [GCKMediaTrack]? {
            //The media tracks represent the different audio streams and also the subtitles (unmutable)
            //With the current sdk, filling the audio channels-tracks manually does not work (they appear, but don't load)
            //After loadMedia, it will load the audio channells from m3u8 automatically, but leaving out possibility to start the video in the selected language

            //Load subtitles
            if self.hasSubTitles() {
                var mediaTracks: [GCKMediaTrack] = [GCKMediaTrack]()

                let mediaTrack = GCKMediaTrack(identifier: kSubsIdentifier,
                                               contentIdentifier: self.streamingCC,
                                               contentType: kKeySubtitle,
                                               type: .text,
                                               textSubtype: .subtitles,
                                               name: "English CC",
                                               languageCode: "en-us",
                                               customData: nil)
                mediaTracks.append(mediaTrack)

                return mediaTracks
            }

            return nil
        }

        let textTrackStyle : GCKMediaTextTrackStyle = GCKMediaTextTrackStyle.createDefault()
        textTrackStyle.backgroundColor = GCKColor(uiColor: .clear)

        let info = GCKMediaInformation(
            contentID: "\(self.id)",
            streamType: .buffered,
            contentType: "application/x-mpegurl",
            metadata: metadata,
            streamDuration: duration,
            mediaTracks: __getMediaTracks(),
            textTrackStyle: textTrackStyle,
            customData: nil)
        return info
    }

}

@objc public enum BasicPlayerState: Int {
    case null = 0
    case paused = 1
    case playing = 2
    case failed = 3

    func toString() -> String {
        switch self {
        case .null: return "null" // stopped
        case .paused: return "paused"
        case .playing: return "playing"
        case .failed: return "failed"
        }
    }
}

enum BasicPlayerStreamType {
    case live
    case vod
}

public class Event<T> {
    public typealias EventHandler = (T) -> ()
    public var subscriptions = [Subscription<T>]()

    @discardableResult func subscribe(_ target: AnyObject, _ handler: @escaping EventHandler) -> Subscription<T> {
        let subscription = Subscription<T>(target: target, event: self, handler: handler)
        subscriptions.append(subscription)
        return subscription
    }

    func unsubscribe(_ target: AnyObject) {
        subscriptions = subscriptions.filter { $0.target !== target }
    }

    @discardableResult func subscribe(_ handler: @escaping EventHandler) -> Subscription<T> {
        let subscription = Subscription<T>(target: self, event: self, handler: handler)
        subscriptions.append(subscription)
        return subscription
    }

    func unsubscribe(_ subscription: Subscription<T>) {
        subscriptions = subscriptions.filter { $0 !== subscription }
    }

    func trigger(_ value: T) {
        subscriptions.forEach { (subscription) in
            subscription.handler(value)
        }
    }

    func dispose() {
        subscriptions = []
    }    
}

public class Subscription<T> {
    weak var target: AnyObject? = nil
    let event: Event<T>
    let handler: (T) -> ()

    init(target: AnyObject, event: Event<T>, handler: @escaping (T) -> ()) {
        self.target = target
        self.event = event
        self.handler = handler
    }

    func dispose() {
        event.unsubscribe(self)
    }
}