URLHandler.swift 8.21 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*****************************************************************************
 * URLHandler.swift
 * VLC for iOS
 *****************************************************************************
 * Copyright (c) 2018 VideoLAN. All rights reserved.
 * $Id$
 *
 * Authors: Carola Nitz <caro # videolan.org>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/

import Foundation

15
@objc public protocol VLCURLHandler {
Soomin Lee's avatar
Soomin Lee committed
16 17
    func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool
    func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
}

@objc class URLHandlers: NSObject {
    @objc static let googleURLHandler = GoogleURLHandler()

    @objc static let handlers =
        [
            googleURLHandler,
            DropBoxURLHandler(),
            FileURLHandler(),
            XCallbackURLHandler(),
            VLCCallbackURLHandler(),
            ElseCallbackURLHandler()
        ]
}

class DropBoxURLHandler: NSObject, VLCURLHandler {

Soomin Lee's avatar
Soomin Lee committed
36
    @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
37 38 39
        return url.scheme == "db-a60fc6qj9zdg7bw"
    }

Soomin Lee's avatar
Soomin Lee committed
40
    @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
41 42 43 44 45 46 47 48 49 50 51 52 53 54
        let authResult = DBClientsManager.handleRedirectURL(url)

        if  let authResult = authResult, authResult.isSuccess() == true {
            //TODO:update Dropboxcontrollers
            return true
        }
        return false
    }
}

class GoogleURLHandler: NSObject, VLCURLHandler {

    @objc var currentGoogleAuthorizationFlow: OIDAuthorizationFlowSession?

Soomin Lee's avatar
Soomin Lee committed
55
    @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
56 57 58
        return url.scheme == "com.googleusercontent.apps.CLIENT"
    }

Soomin Lee's avatar
Soomin Lee committed
59
    @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
60 61 62 63 64 65 66 67 68 69
        if currentGoogleAuthorizationFlow?.resumeAuthorizationFlow(with: url) == true {
            currentGoogleAuthorizationFlow = nil
            return true
        }
        return false
    }
}

class FileURLHandler: NSObject, VLCURLHandler {

Soomin Lee's avatar
Soomin Lee committed
70
    @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
71 72 73
        return url.isFileURL
    }

Soomin Lee's avatar
Soomin Lee committed
74
    @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
75 76
        let subclass = DocumentClass(fileURL: url)
        subclass.open { _ in
77
            self.play(url: url) { _ in
78 79 80 81 82 83 84 85 86
                subclass.close(completionHandler: nil)
            }
        }
        return true
    }
}

class XCallbackURLHandler: NSObject, VLCURLHandler {

Soomin Lee's avatar
Soomin Lee committed
87
    @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
88 89 90
        return url.scheme == "vlc-x-callback" || url.scheme == "x-callback-url"
    }

Soomin Lee's avatar
Soomin Lee committed
91
    @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
        let action = url.path.replacingOccurrences(of: "/", with: "")
        var movieURL: URL?
        var successCallback: URL?
        var errorCallback: URL?
        var fileName: String?
        guard let query = url.query else {
            assertionFailure("no query")
            return false
        }
        for entry in query.components(separatedBy: "&") {
            let components = entry.components(separatedBy: "=")
            if components.count < 2 {
                continue
            }

            if let key = components.first, let value = components[1].removingPercentEncoding {
                if key == "url"{
                    movieURL = URL(string: value)
                } else if key == "filename" {
                    fileName = value
                } else if key == "x-success" {
                    successCallback = URL(string: value)
                } else if key == "x-error" {
                    errorCallback = URL(string: value)
                }
            } else {
                assertionFailure("no key or app value")
            }
        }
        if action == "stream", let movieURL = movieURL {
            play(url: movieURL) { success in
                guard let callback = success ? successCallback : errorCallback else {
                    assertionFailure("no CallbackURL")
                    return
                }
                if #available(iOS 10, *) {
Soomin Lee's avatar
Soomin Lee committed
128
                    UIApplication.shared.open(callback, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
129 130 131 132 133 134 135 136 137 138 139 140 141
                } else {
                    UIApplication.shared.openURL(callback)
                }
            }
            return true
        } else if action == "download", let movieURL = movieURL {
            downloadMovie(from:movieURL, fileNameOfMedia:fileName)
            return true
        }
        return false
    }
}

142
public class VLCCallbackURLHandler: NSObject, VLCURLHandler {
143

Soomin Lee's avatar
Soomin Lee committed
144
    @objc public func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
145 146 147 148
        return url.scheme == "vlc"
    }

    // Safari fixes URLs like "vlc://http://example.org" to "vlc://http//example.org"
149
    public func transformVLCURL(_ url: URL) -> URL {
150 151 152 153 154 155 156 157 158
        var parsedString = url.absoluteString.replacingOccurrences(of: "vlc://", with: "")
        if let location = parsedString.range(of: "//"), parsedString[parsedString.index(location.lowerBound, offsetBy: -1)] != ":" {
            parsedString = "\(parsedString[parsedString.startIndex..<location.lowerBound])://\(parsedString[location.upperBound...])"
        } else if !parsedString.hasPrefix("http://") && !parsedString.hasPrefix("https://") && !parsedString.hasPrefix("ftp://") {
            parsedString = "http://\(parsedString)"
        }
        return URL(string: parsedString)!
    }

Soomin Lee's avatar
Soomin Lee committed
159
    public func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
160 161 162 163 164 165

        let transformedURL = transformVLCURL(url)
        let scheme = transformedURL.scheme
        if scheme == "http" || scheme == "https" || scheme == "ftp" {
            let alert = UIAlertController(title: NSLocalizedString("OPEN_STREAM_OR_DOWNLOAD", comment:""), message: url.absoluteString, preferredStyle: .alert)
            let downloadAction = UIAlertAction(title: NSLocalizedString("BUTTON_DOWNLOAD", comment:""), style: .default) { _ in
166
                self.downloadMovie(from:transformedURL, fileNameOfMedia:nil)
167 168 169
            }
            alert.addAction(downloadAction)
            let playAction = UIAlertAction(title: NSLocalizedString("PLAY_BUTTON", comment:""), style: .default) { _ in
170
                self.play(url: transformedURL, completion: nil)
171 172 173 174
            }
            alert.addAction(playAction)
            alert.show(UIApplication.shared.keyWindow!.rootViewController!, sender: nil)
        } else {
175
            self.play(url: transformedURL, completion: nil)
176 177 178 179 180 181
        }
        return true
    }
}

class ElseCallbackURLHandler: NSObject, VLCURLHandler {
Soomin Lee's avatar
Soomin Lee committed
182
    @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
183 184 185
        return true
    }

Soomin Lee's avatar
Soomin Lee committed
186
    func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
187
        self.play(url: url, completion: nil)
188 189 190 191
        return true
    }
}

192 193 194 195 196 197 198 199
extension VLCURLHandler {
    // TODO: This code should probably not live here
    func play(url: URL, completion: ((Bool) -> Void)?) {
        let vpc = VLCPlaybackController.sharedInstance()
        vpc.fullscreenSessionRequested = true
        if let mediaList = VLCMediaList(array: [VLCMedia(url: url)]) {
            vpc.playMediaList(mediaList, firstIndex: 0, subtitlesFilePath: nil, completion: completion)
        }
200 201
    }

202 203 204
    func downloadMovie(from url: URL, fileNameOfMedia fileName: String?) {
        VLCDownloadViewController.sharedInstance().addURL(toDownloadList: url, fileNameOfMedia: fileName)
    }
205
}
Soomin Lee's avatar
Soomin Lee committed
206 207 208 209 210

// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
	return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}