From b6839991f5a9f378d4a2b05ad81562c8875d86e6 Mon Sep 17 00:00:00 2001 From: Danis Ziganshin Date: Wed, 28 Dec 2016 16:02:37 +0300 Subject: [PATCH 1/2] Transfer framework to the Swift 3 --- Pantomime.xcodeproj/project.pbxproj | 39 +++++++++---- .../xcschemes/Pantomime (OSX).xcscheme | 2 +- .../xcschemes/Pantomime (iOS).xcscheme | 2 +- .../xcschemes/Pantomime (tvOS).xcscheme | 2 +- PantomimeTests/PantomimeTests.swift | 58 +++++++++---------- PantomimeTests/ReaderTests.swift | 12 ++-- sources/FileBufferedReader.swift | 6 +- sources/ManifestBuilder.swift | 52 ++++++++--------- sources/MasterPlaylist.swift | 10 ++-- sources/MediaPlaylist.swift | 24 ++++---- sources/MediaSegment.swift | 18 +++--- sources/NSURLExtension.swift | 10 ++-- sources/ReaderBuilder.swift | 26 ++++----- sources/StreamReader.swift | 34 +++++------ sources/StringBufferedReader.swift | 8 +-- sources/StringExtensions.swift | 10 ++-- sources/URLBufferedReader.swift | 18 +++--- 17 files changed, 173 insertions(+), 158 deletions(-) diff --git a/Pantomime.xcodeproj/project.pbxproj b/Pantomime.xcodeproj/project.pbxproj index 9cbdc09..f438d00 100755 --- a/Pantomime.xcodeproj/project.pbxproj +++ b/Pantomime.xcodeproj/project.pbxproj @@ -300,19 +300,22 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Thomas Christensen"; TargetAttributes = { 9EDCE3E31C09D211002FA4A7 = { CreatedOnToolsVersion = 7.1.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 0820; + ProvisioningStyle = Manual; }; 9EDCE3ED1C09D211002FA4A7 = { CreatedOnToolsVersion = 7.1.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 0820; + ProvisioningStyle = Manual; }; ACE597861C18830D0031451F = { LastSwiftMigration = 0800; + ProvisioningStyle = Manual; }; ACE597971C18832E0031451F = { LastSwiftMigration = 0800; @@ -501,8 +504,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -550,8 +555,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -584,7 +591,9 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -595,7 +604,7 @@ PRODUCT_NAME = Pantomime; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -604,7 +613,9 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -615,7 +626,7 @@ PRODUCT_NAME = Pantomime; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -623,11 +634,12 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PantomimeTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nordija.PantomimeTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -635,12 +647,13 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = PantomimeTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nordija.PantomimeTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -648,7 +661,9 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -662,7 +677,7 @@ SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; VALID_ARCHS = "$(ARCHS_STANDARD)"; }; name = Debug; @@ -671,7 +686,9 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -685,7 +702,7 @@ SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; VALID_ARCHS = "$(ARCHS_STANDARD)"; }; name = Release; @@ -709,7 +726,7 @@ SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; VALID_ARCHS = "i386 x86_64"; }; name = Debug; @@ -733,7 +750,7 @@ SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; VALID_ARCHS = "i386 x86_64"; }; name = Release; diff --git a/Pantomime.xcodeproj/xcshareddata/xcschemes/Pantomime (OSX).xcscheme b/Pantomime.xcodeproj/xcshareddata/xcschemes/Pantomime (OSX).xcscheme index 5bc1997..fedb065 100755 --- a/Pantomime.xcodeproj/xcshareddata/xcschemes/Pantomime (OSX).xcscheme +++ b/Pantomime.xcodeproj/xcshareddata/xcschemes/Pantomime (OSX).xcscheme @@ -1,6 +1,6 @@ Void in XCTAssertNotNil(segment.sequence) @@ -43,8 +43,8 @@ class PantomimeTests: XCTestCase { } func testParseMasterPlaylist() { - let bundle = NSBundle(forClass: self.dynamicType) - let path = bundle.pathForResource("master", ofType: "m3u8")! + let bundle = Bundle(for: type(of: self)) + let path = bundle.path(forResource: "master", ofType: "m3u8")! let manifestBuilder = ManifestBuilder() @@ -64,12 +64,12 @@ class PantomimeTests: XCTestCase { * fetch the manifest files and then parse the text. */ func testParseFromString() { - let bundle = NSBundle(forClass: self.dynamicType) - let file = bundle.pathForResource("master", ofType: "m3u8")! - let path = NSURL(fileURLWithPath: file) + let bundle = Bundle(for: type(of: self)) + let file = bundle.path(forResource: "master", ofType: "m3u8")! + let path = URL(fileURLWithPath: file) do { - let manifestText = try String(contentsOfURL: path, encoding: NSUTF8StringEncoding) + let manifestText = try String(contentsOf: path, encoding: String.Encoding.utf8) let manifestBuilder = ManifestBuilder() let masterPlaylist = manifestBuilder.parseMasterPlaylistFromString(manifestText) XCTAssert(masterPlaylist.playlists.count == 4) @@ -85,40 +85,40 @@ class PantomimeTests: XCTestCase { // Keep baseURL separate to contruct the nested media playlist URL's let baseURL = "http://devimages.apple.com/iphone/samples/bipbop" let path = "bipbopall.m3u8" - let URL = NSURL(string: baseURL + "/" + path)! + let URL = Foundation.URL(string: baseURL + "/" + path)! XCTAssertEqual("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", URL.absoluteString) - let expectation = expectationWithDescription("Testing parsing of the apple bipbop HTTP Live Stream sample") + let expectation = self.expectation(description: "Testing parsing of the apple bipbop HTTP Live Stream sample") - let session = NSURLSession.sharedSession() + let session = URLSession.shared // Request master playlist - let task = session.dataTaskWithURL(URL) { + let task = session.dataTask(with: URL, completionHandler: { data, response, error in XCTAssertNotNil(data, "data should not be nil") XCTAssertNil(error, "error should be nil") - if let httpResponse = response as? NSHTTPURLResponse, - responseURL = httpResponse.URL, - mimeType = httpResponse.MIMEType { + if let httpResponse = response as? HTTPURLResponse, + let responseURL = httpResponse.url, + let mimeType = httpResponse.mimeType { XCTAssertEqual(responseURL.absoluteString, URL.absoluteString, "No redirect expected") XCTAssertEqual(httpResponse.statusCode, 200, "HTTP response status code should be 200") XCTAssertEqual(mimeType, "audio/x-mpegurl", "HTTP response content type should be text/html") // Parse master playlist and perform verification of it - if let dataFound = data, manifestText = String(data: dataFound, encoding: NSUTF8StringEncoding) { + if let dataFound = data, let manifestText = String(data: dataFound, encoding: String.Encoding.utf8) { let masterPlaylist = manifestBuilder.parseMasterPlaylistFromString(manifestText, onMediaPlaylist: { (mep: MediaPlaylist) -> Void in // Deduct full media playlist URL from path - if let path = mep.path, mepURL = NSURL(string: baseURL + "/" + path) { + if let path = mep.path, let mepURL = Foundation.URL(string: baseURL + "/" + path) { // Request each found media playlist - let mepTask = session.dataTaskWithURL(mepURL) { + let mepTask = session.dataTask(with: mepURL, completionHandler: { mepData, mepResponse, mepError in XCTAssertNotNil(mepData, "data should not be nil") @@ -126,16 +126,16 @@ class PantomimeTests: XCTestCase { // Parse the media playlist and perform validation if let mepDataFound = mepData, - mepManifest = String(data: mepDataFound, encoding: NSUTF8StringEncoding) { + let mepManifest = String(data: mepDataFound, encoding: String.Encoding.utf8) { let mediaPlaylist = manifestBuilder.parseMediaPlaylistFromString(mepManifest) XCTAssertEqual(181, mediaPlaylist.segments.count) } // In case we have requested, parsed and validated the last one - if path.containsString("gear4/prog_index.m3u8") { + if path.contains("gear4/prog_index.m3u8") { expectation.fulfill() } - } + }) mepTask.resume() } @@ -146,11 +146,11 @@ class PantomimeTests: XCTestCase { } else { XCTFail("Response was not NSHTTPURLResponse") } - } + }) task.resume() - waitForExpectationsWithTimeout(task.originalRequest!.timeoutInterval) { + waitForExpectations(timeout: task.originalRequest!.timeoutInterval) { error in if let error = error { XCTFail("Error: \(error.localizedDescription)") @@ -164,8 +164,8 @@ class PantomimeTests: XCTestCase { // Check using String with contentsOfURL do { - if let url = NSURL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { - let content = try String(contentsOfURL: url, encoding: NSUTF8StringEncoding) + if let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { + let content = try String(contentsOf: url, encoding: String.Encoding.utf8) let master = builder.parseMasterPlaylistFromString(content) XCTAssertEqual(4, master.playlists.count, "Number of media playlists in master does not match") } else { @@ -178,7 +178,7 @@ class PantomimeTests: XCTestCase { func testSimpleFullParse() { let builder = ManifestBuilder() - if let url = NSURL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { + if let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { let manifest = builder.parse(url) XCTAssertEqual(4, manifest.playlists.count) } @@ -186,7 +186,7 @@ class PantomimeTests: XCTestCase { func testFullParse() { let builder = ManifestBuilder() - if let url = NSURL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { + if let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { let manifest = builder.parse(url, onMediaPlaylist: { (media: MediaPlaylist) -> Void in XCTAssertNotNil(media.path) @@ -203,7 +203,7 @@ class PantomimeTests: XCTestCase { func testFullParseWithFullPathInManifests() { let builder = ManifestBuilder() - if let url = NSURL(string: "https://mnmedias.api.telequebec.tv/m3u8/29880.m3u8") { + if let url = URL(string: "https://mnmedias.api.telequebec.tv/m3u8/29880.m3u8") { let manifest = builder.parse(url, onMediaPlaylist: { (media: MediaPlaylist) -> Void in XCTAssertNotNil(media.path) diff --git a/PantomimeTests/ReaderTests.swift b/PantomimeTests/ReaderTests.swift index abd23ec..263de10 100644 --- a/PantomimeTests/ReaderTests.swift +++ b/PantomimeTests/ReaderTests.swift @@ -9,25 +9,25 @@ import XCTest class ReaderTests: XCTestCase { func testReaderBuilder() { do { - let stringReader = try ReaderBuilder.createReader(.STRINGREADER, reference: "This is a line\nThis is another") + let stringReader = try ReaderBuilder.createReader(.stringreader, reference: "This is a line\nThis is another") XCTAssert(stringReader is StringBufferedReader) XCTAssertEqual("This is a line", stringReader.readLine()) XCTAssertEqual("This is another", stringReader.readLine()) XCTAssertNil(stringReader.readLine()) XCTAssertNil(stringReader.readLine()) - let bundle = NSBundle(forClass: self.dynamicType) - let path = bundle.pathForResource("media", ofType: "m3u8")! - let fileReader = try ReaderBuilder.createReader(.FILEREADER, reference: path) + let bundle = Bundle(for: type(of: self)) + let path = bundle.path(forResource: "media", ofType: "m3u8")! + let fileReader = try ReaderBuilder.createReader(.filereader, reference: path) XCTAssert(fileReader is FileBufferedReader) XCTAssertEqual("#EXTM3U", fileReader.readLine()) XCTAssertEqual("#This is a comment", fileReader.readLine()) for _ in 1...10 { - fileReader.readLine() + _ = fileReader.readLine() } XCTAssertNil(fileReader.readLine()) - let httpReader = try ReaderBuilder.createReader(.HTTPREADER, reference: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") + let httpReader = try ReaderBuilder.createReader(.httpreader, reference: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") XCTAssert(httpReader is URLBufferedReader) XCTAssertEqual("#EXTM3U", httpReader.readLine()) XCTAssertEqual("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000", httpReader.readLine()) diff --git a/sources/FileBufferedReader.swift b/sources/FileBufferedReader.swift index 2e263e3..b5240c2 100644 --- a/sources/FileBufferedReader.swift +++ b/sources/FileBufferedReader.swift @@ -5,7 +5,7 @@ import Foundation -public class FileBufferedReader: BufferedReader { +open class FileBufferedReader: BufferedReader { var _fileName: String var streamReader: StreamReader @@ -14,11 +14,11 @@ public class FileBufferedReader: BufferedReader { streamReader = StreamReader(path: _fileName)! } - public func close() { + open func close() { streamReader.close() } - public func readLine() -> String? { + open func readLine() -> String? { return streamReader.nextLine() } } diff --git a/sources/ManifestBuilder.swift b/sources/ManifestBuilder.swift index 780a089..2ad1367 100644 --- a/sources/ManifestBuilder.swift +++ b/sources/ManifestBuilder.swift @@ -9,15 +9,15 @@ import Foundation * Parses HTTP Live Streaming manifest files * Use a BufferedReader to let the parser read from various sources. */ -public class ManifestBuilder { +open class ManifestBuilder { public init() {} /** * Parses Master playlist manifests */ - private func parseMasterPlaylist(reader: BufferedReader, onMediaPlaylist: - ((playlist: MediaPlaylist) -> Void)?) -> MasterPlaylist { + fileprivate func parseMasterPlaylist(_ reader: BufferedReader, onMediaPlaylist: + ((_ playlist: MediaPlaylist) -> Void)?) -> MasterPlaylist { var masterPlaylist = MasterPlaylist() var currentMediaPlaylist: MediaPlaylist? @@ -59,7 +59,7 @@ public class ManifestBuilder { currentMediaPlaylistExist.masterPlaylist = masterPlaylist masterPlaylist.addPlaylist(currentMediaPlaylistExist) if let callableOnMediaPlaylist = onMediaPlaylist { - callableOnMediaPlaylist(playlist: currentMediaPlaylistExist) + callableOnMediaPlaylist(currentMediaPlaylistExist) } } } @@ -71,8 +71,8 @@ public class ManifestBuilder { /** * Parses Media Playlist manifests */ - private func parseMediaPlaylist(reader: BufferedReader, mediaPlaylist: MediaPlaylist = MediaPlaylist(), - onMediaSegment: ((segment: MediaSegment) -> Void)?) -> MediaPlaylist { + fileprivate func parseMediaPlaylist(_ reader: BufferedReader, mediaPlaylist: MediaPlaylist = MediaPlaylist(), + onMediaSegment: ((_ segment: MediaSegment) -> Void)?) -> MediaPlaylist { var currentSegment: MediaSegment? var currentURI: String? var currentSequence = 0 @@ -129,7 +129,7 @@ public class ManifestBuilder { print("Failed to parse the segment duration and title. Line = \(line)") } } else if line.hasPrefix("#EXT-X-BYTERANGE") { - if line.containsString("@") { + if line.contains("@") { do { let subrangeLength = try line.replace("(.*):(\\d.*)@(.*)", replacement: "$2") let subrangeStart = try line.replace("(.*):(\\d.*)@(.*)", replacement: "$3") @@ -163,7 +163,7 @@ public class ManifestBuilder { currentSequence += 1 mediaPlaylist.addSegment(currentSegmentExists) if let callableOnMediaSegment = onMediaSegment { - callableOnMediaSegment(segment: currentSegmentExists) + callableOnMediaSegment(currentSegmentExists) } } } @@ -177,8 +177,8 @@ public class ManifestBuilder { * * Convenience method that uses a StringBufferedReader as source for the manifest. */ - public func parseMasterPlaylistFromString(string: String, onMediaPlaylist: - ((playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { + open func parseMasterPlaylistFromString(_ string: String, onMediaPlaylist: + ((_ playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { return parseMasterPlaylist(StringBufferedReader(string: string), onMediaPlaylist: onMediaPlaylist) } @@ -187,8 +187,8 @@ public class ManifestBuilder { * * Convenience method that uses a FileBufferedReader as source for the manifest. */ - public func parseMasterPlaylistFromFile(path: String, onMediaPlaylist: - ((playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { + open func parseMasterPlaylistFromFile(_ path: String, onMediaPlaylist: + ((_ playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { return parseMasterPlaylist(FileBufferedReader(path: path), onMediaPlaylist: onMediaPlaylist) } @@ -197,8 +197,8 @@ public class ManifestBuilder { * * Convenience method that uses a URLBufferedReader as source for the manifest. */ - public func parseMasterPlaylistFromURL(url: NSURL, onMediaPlaylist: - ((playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { + open func parseMasterPlaylistFromURL(_ url: URL, onMediaPlaylist: + ((_ playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { return parseMasterPlaylist(URLBufferedReader(uri: url), onMediaPlaylist: onMediaPlaylist) } @@ -207,8 +207,8 @@ public class ManifestBuilder { * * Convenience method that uses a StringBufferedReader as source for the manifest. */ - public func parseMediaPlaylistFromString(string: String, mediaPlaylist: MediaPlaylist = MediaPlaylist(), - onMediaSegment:((segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { + open func parseMediaPlaylistFromString(_ string: String, mediaPlaylist: MediaPlaylist = MediaPlaylist(), + onMediaSegment:((_ segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { return parseMediaPlaylist(StringBufferedReader(string: string), mediaPlaylist: mediaPlaylist, onMediaSegment: onMediaSegment) } @@ -218,8 +218,8 @@ public class ManifestBuilder { * * Convenience method that uses a FileBufferedReader as source for the manifest. */ - public func parseMediaPlaylistFromFile(path: String, mediaPlaylist: MediaPlaylist = MediaPlaylist(), - onMediaSegment: ((segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { + open func parseMediaPlaylistFromFile(_ path: String, mediaPlaylist: MediaPlaylist = MediaPlaylist(), + onMediaSegment: ((_ segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { return parseMediaPlaylist(FileBufferedReader(path: path), mediaPlaylist: mediaPlaylist, onMediaSegment: onMediaSegment) } @@ -229,8 +229,8 @@ public class ManifestBuilder { * * Convenience method that uses a URLBufferedReader as source for the manifest. */ - public func parseMediaPlaylistFromURL(url: NSURL, mediaPlaylist: MediaPlaylist = MediaPlaylist(), - onMediaSegment: ((segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { + open func parseMediaPlaylistFromURL(_ url: URL, mediaPlaylist: MediaPlaylist = MediaPlaylist(), + onMediaSegment: ((_ segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { return parseMediaPlaylist(URLBufferedReader(uri: url), mediaPlaylist: mediaPlaylist, onMediaSegment: onMediaSegment) } @@ -238,9 +238,9 @@ public class ManifestBuilder { /** * Parses the master manifest found at the URL and all the referenced media playlist manifests recursively. */ - public func parse(url: NSURL, onMediaPlaylist: - ((playlist: MediaPlaylist) -> Void)? = nil, onMediaSegment: - ((segment: MediaSegment) -> Void)? = nil) -> MasterPlaylist { + open func parse(_ url: URL, onMediaPlaylist: + ((_ playlist: MediaPlaylist) -> Void)? = nil, onMediaSegment: + ((_ segment: MediaSegment) -> Void)? = nil) -> MasterPlaylist { // Parse master let master = parseMasterPlaylistFromURL(url, onMediaPlaylist: onMediaPlaylist) for playlist in master.playlists { @@ -249,14 +249,14 @@ public class ManifestBuilder { // Detect if manifests are referred to with protocol if path.hasPrefix("http") || path.hasPrefix("file") { // Full path used - if let mediaURL = NSURL(string: path) { - parseMediaPlaylistFromURL(mediaURL, + if let mediaURL = URL(string: path) { + _ = parseMediaPlaylistFromURL(mediaURL, mediaPlaylist: playlist, onMediaSegment: onMediaSegment) } } else { // Relative path used if let mediaURL = url.URLByReplacingLastPathComponent(path) { - parseMediaPlaylistFromURL(mediaURL, + _ = parseMediaPlaylistFromURL(mediaURL, mediaPlaylist: playlist, onMediaSegment: onMediaSegment) } } diff --git a/sources/MasterPlaylist.swift b/sources/MasterPlaylist.swift index 18e33db..d65ef35 100644 --- a/sources/MasterPlaylist.swift +++ b/sources/MasterPlaylist.swift @@ -8,24 +8,24 @@ import Foundation -public class MasterPlaylist { +open class MasterPlaylist { var playlists = [MediaPlaylist]() - public var path: String? + open var path: String? public init() {} - public func addPlaylist(playlist: MediaPlaylist) { + open func addPlaylist(_ playlist: MediaPlaylist) { playlists.append(playlist) } - public func getPlaylist(index: Int) -> MediaPlaylist? { + open func getPlaylist(_ index: Int) -> MediaPlaylist? { if index >= playlists.count { return nil } return playlists[index] } - public func getPlaylistCount() -> Int { + open func getPlaylistCount() -> Int { return playlists.count } } diff --git a/sources/MediaPlaylist.swift b/sources/MediaPlaylist.swift index 5e373eb..7c884c8 100644 --- a/sources/MediaPlaylist.swift +++ b/sources/MediaPlaylist.swift @@ -5,37 +5,37 @@ import Foundation -public class MediaPlaylist { +open class MediaPlaylist { var masterPlaylist: MasterPlaylist? - public var programId: Int = 0 - public var bandwidth: Int = 0 - public var path: String? - public var version: Int? - public var targetDuration: Int? - public var mediaSequence: Int? + open var programId: Int = 0 + open var bandwidth: Int = 0 + open var path: String? + open var version: Int? + open var targetDuration: Int? + open var mediaSequence: Int? var segments = [MediaSegment]() public init() { } - public func addSegment(segment: MediaSegment) { + open func addSegment(_ segment: MediaSegment) { segments.append(segment) } - public func getSegment(index: Int) -> MediaSegment? { + open func getSegment(_ index: Int) -> MediaSegment? { if index >= segments.count { return nil } return segments[index] } - public func getSegmentCount() -> Int { + open func getSegmentCount() -> Int { return segments.count } - public func duration() -> Float { + open func duration() -> Float { var dur: Float = 0.0 for item in segments { dur = dur + item.duration! @@ -43,7 +43,7 @@ public class MediaPlaylist { return dur } - public func getMaster() -> MasterPlaylist? { + open func getMaster() -> MasterPlaylist? { return self.masterPlaylist } } diff --git a/sources/MediaSegment.swift b/sources/MediaSegment.swift index 1b7e944..d5e3af0 100644 --- a/sources/MediaSegment.swift +++ b/sources/MediaSegment.swift @@ -5,21 +5,21 @@ import Foundation -public class MediaSegment { +open class MediaSegment { var mediaPlaylist: MediaPlaylist? - public var duration: Float? - public var sequence: Int = 0 - public var subrangeLength: Int? - public var subrangeStart: Int? - public var title: String? - public var discontinuity: Bool = false - public var path: String? + open var duration: Float? + open var sequence: Int = 0 + open var subrangeLength: Int? + open var subrangeStart: Int? + open var title: String? + open var discontinuity: Bool = false + open var path: String? public init() { } - public func getMediaPlaylist() -> MediaPlaylist? { + open func getMediaPlaylist() -> MediaPlaylist? { return self.mediaPlaylist } } diff --git a/sources/NSURLExtension.swift b/sources/NSURLExtension.swift index 828e5ce..dfc059c 100644 --- a/sources/NSURLExtension.swift +++ b/sources/NSURLExtension.swift @@ -7,7 +7,7 @@ import Foundation // Extend the NSURL object with helpers -public extension NSURL { +public extension URL { /** Replaces the last path component of the URL with the path component and returns a new URL or nil. @@ -16,10 +16,8 @@ public extension NSURL { - Returns: A new URL object or nil */ @available(iOS 4.0, *) - public func URLByReplacingLastPathComponent(pathComponent: String) -> NSURL? { - if let tmpurl = self.URLByDeletingLastPathComponent { - return tmpurl.URLByAppendingPathComponent(pathComponent) - } - return nil + public func URLByReplacingLastPathComponent(_ pathComponent: String) -> URL? { + let tmpurl = self.deletingLastPathComponent() + return tmpurl.appendingPathComponent(pathComponent) } } diff --git a/sources/ReaderBuilder.swift b/sources/ReaderBuilder.swift index f55fdbe..248e844 100644 --- a/sources/ReaderBuilder.swift +++ b/sources/ReaderBuilder.swift @@ -5,40 +5,40 @@ import Foundation -public class ReaderBuilder { +open class ReaderBuilder { - enum ReaderBuilderError: ErrorType { - case IllegalReference(reference:String) + enum ReaderBuilderError: Error { + case illegalReference(reference:String) } enum ReaderTypes { - case STRINGREADER, HTTPREADER, FILEREADER + case stringreader, httpreader, filereader } - static func createURLReader(reference: NSURL) -> BufferedReader { + static func createURLReader(_ reference: URL) -> BufferedReader { return URLBufferedReader(uri: reference) } - static func createStringReader(reference: String) -> BufferedReader { + static func createStringReader(_ reference: String) -> BufferedReader { return StringBufferedReader(string: reference) } - static func createFileReader(reference: String) -> BufferedReader? { + static func createFileReader(_ reference: String) -> BufferedReader? { return FileBufferedReader(path: reference) } - static func createReader(reader: ReaderTypes, reference: String) throws -> BufferedReader { + static func createReader(_ reader: ReaderTypes, reference: String) throws -> BufferedReader { switch reader { - case .STRINGREADER: + case .stringreader: return StringBufferedReader(string: reference) - case .FILEREADER: + case .filereader: return FileBufferedReader(path: reference) - case .HTTPREADER: - if let uriOK = NSURL(string: reference) { + case .httpreader: + if let uriOK = URL(string: reference) { return URLBufferedReader(uri: uriOK) } else { - throw ReaderBuilderError.IllegalReference(reference: reference) + throw ReaderBuilderError.illegalReference(reference: reference) } } } diff --git a/sources/StreamReader.swift b/sources/StreamReader.swift index 10613fd..b5eb083 100644 --- a/sources/StreamReader.swift +++ b/sources/StreamReader.swift @@ -7,21 +7,21 @@ import Foundation class StreamReader { - let encoding: UInt + let encoding: String.Encoding let chunkSize: Int - var fileHandle: NSFileHandle! + var fileHandle: FileHandle! let buffer: NSMutableData! - let delimData: NSData! + let delimData: Data! var atEof: Bool = false - init?(path: String, delimiter: String = "\n", encoding: UInt = NSUTF8StringEncoding, chunkSize: Int = 4096) { + init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8, chunkSize: Int = 4096) { self.chunkSize = chunkSize self.encoding = encoding - if let fileHandle = NSFileHandle(forReadingAtPath: path), - delimData = delimiter.dataUsingEncoding(encoding), - buffer = NSMutableData(capacity: chunkSize) { + if let fileHandle = FileHandle(forReadingAtPath: path), + let delimData = delimiter.data(using: encoding), + let buffer = NSMutableData(capacity: chunkSize) { self.fileHandle = fileHandle self.delimData = delimData self.buffer = buffer @@ -47,15 +47,15 @@ class StreamReader { // Read data chunks from file until a line delimiter is found: - var range = buffer.rangeOfData(delimData, options: [], range: NSRange(location: 0, length: buffer.length)) + var range = buffer.range(of: delimData, options: [], in: NSRange(location: 0, length: buffer.length)) while range.location == NSNotFound { - let tmpData = fileHandle.readDataOfLength(chunkSize) - if tmpData.length == 0 { + let tmpData = fileHandle.readData(ofLength: chunkSize) + if tmpData.count == 0 { // EOF or read error. atEof = true if buffer.length > 0 { // Buffer contains last line in file (not terminated by delimiter). - let line = NSString(data: buffer, encoding: encoding) + let line = NSString(data: buffer as Data, encoding: encoding.rawValue) buffer.length = 0 return line as String? @@ -63,15 +63,15 @@ class StreamReader { // No more lines. return nil } - buffer.appendData(tmpData) - range = buffer.rangeOfData(delimData, options: [], range: NSRange(location: 0, length: buffer.length)) + buffer.append(tmpData) + range = buffer.range(of: delimData, options: [], in: NSRange(location: 0, length: buffer.length)) } // Convert complete line (excluding the delimiter) to a string: - let line = NSString(data: buffer.subdataWithRange(NSRange(location: 0, length: range.location)), - encoding: encoding) + let line = NSString(data: buffer.subdata(with: NSRange(location: 0, length: range.location)), + encoding: encoding.rawValue) // Remove line (and the delimiter) from the buffer: - buffer.replaceBytesInRange( NSRange(location: 0, length: range.location + range.length), + buffer.replaceBytes( in: NSRange(location: 0, length: range.location + range.length), withBytes: nil, length: 0) return line as String? @@ -79,7 +79,7 @@ class StreamReader { /// Start reading from the beginning of file. func rewind() -> Void { - fileHandle.seekToFileOffset(0) + fileHandle.seek(toFileOffset: 0) buffer.length = 0 atEof = false } diff --git a/sources/StringBufferedReader.swift b/sources/StringBufferedReader.swift index b10f791..90618d5 100644 --- a/sources/StringBufferedReader.swift +++ b/sources/StringBufferedReader.swift @@ -9,19 +9,19 @@ import Foundation * Uses a string as a stream and reads it line by line. */ -public class StringBufferedReader: BufferedReader { +open class StringBufferedReader: BufferedReader { var _buffer: [String] var _line: Int public init(string: String) { _line = 0 - _buffer = string.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) + _buffer = string.components(separatedBy: CharacterSet.newlines) } - public func close() { + open func close() { } - public func readLine() -> String? { + open func readLine() -> String? { if _buffer.isEmpty || _buffer.count <= _line { return nil } diff --git a/sources/StringExtensions.swift b/sources/StringExtensions.swift index bc5e653..87f925f 100644 --- a/sources/StringExtensions.swift +++ b/sources/StringExtensions.swift @@ -9,13 +9,13 @@ import Foundation extension String { // String.replace(); similar to JavaScript's String.replace() and Ruby's String.gsub() - func replace(pattern: String, replacement: String) throws -> String { + func replace(_ pattern: String, replacement: String) throws -> String { - let regex = try NSRegularExpression(pattern: pattern, options: [.CaseInsensitive]) + let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) - return regex.stringByReplacingMatchesInString( - self, - options: [.WithTransparentBounds], + return regex.stringByReplacingMatches( + in: self, + options: [.withTransparentBounds], range: NSRange(location: 0, length: self.characters.count), withTemplate: replacement ) diff --git a/sources/URLBufferedReader.swift b/sources/URLBufferedReader.swift index aeb858f..70d11cc 100644 --- a/sources/URLBufferedReader.swift +++ b/sources/URLBufferedReader.swift @@ -9,29 +9,29 @@ import Foundation * Reads the document found at the specified URL in one chunk synchonous * and then lets the readLine function pick it line by line. */ -public class URLBufferedReader: BufferedReader { - var _uri: NSURL +open class URLBufferedReader: BufferedReader { + var _uri: URL var _stringReader: StringBufferedReader - public init(uri: NSURL) { + public init(uri: URL) { _uri = uri _stringReader = StringBufferedReader(string: "") - let request1: NSURLRequest = NSURLRequest(URL: _uri) - let response: AutoreleasingUnsafeMutablePointer = nil + let request1: URLRequest = URLRequest(url: _uri) + let response: AutoreleasingUnsafeMutablePointer? = nil do { - let dataVal = try NSURLConnection.sendSynchronousRequest(request1, returningResponse: response) - let text = String(data: dataVal, encoding: NSUTF8StringEncoding)! + let dataVal = try NSURLConnection.sendSynchronousRequest(request1, returning: response) + let text = String(data: dataVal, encoding: String.Encoding.utf8)! _stringReader = StringBufferedReader(string: text) } catch { print("Failed to make request for content at \(_uri)") } } - public func close() { + open func close() { _stringReader.close() } - public func readLine() -> String? { + open func readLine() -> String? { return _stringReader.readLine() } From b82c939abf961a8542275d8ea713a9deac5e6dde Mon Sep 17 00:00:00 2001 From: Danis Ziganshin Date: Wed, 28 Dec 2016 16:53:45 +0300 Subject: [PATCH 2/2] Edit regex and error handling when parsing program-id and bandwidth --- PantomimeTests/ReaderTests.swift | 5 +++++ sources/ManifestBuilder.swift | 17 +++++++++++------ sources/StringExtensions.swift | 12 +++++++++--- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/PantomimeTests/ReaderTests.swift b/PantomimeTests/ReaderTests.swift index 263de10..cb1078a 100644 --- a/PantomimeTests/ReaderTests.swift +++ b/PantomimeTests/ReaderTests.swift @@ -42,4 +42,9 @@ class ReaderTests: XCTestCase { XCTFail("Not able to construct valid buffered reader instances") } } + + func testBuilder_WithoutProgramID_ShouldNotFail() { + let builder = ManifestBuilder() + _ = builder.parseMasterPlaylistFromString("#EXTM3U\n#EXT-X-VERSION:2\n#EXT-X-STREAM-INF:BANDWIDTH=300000\ntrack_0_300/playlist.m3u8?t=1482930352&h=VBNpRGL+iqMhSUWVV3IQ4w==\n") + } } diff --git a/sources/ManifestBuilder.swift b/sources/ManifestBuilder.swift index 2ad1367..3e46dd0 100644 --- a/sources/ManifestBuilder.swift +++ b/sources/ManifestBuilder.swift @@ -36,16 +36,21 @@ open class ManifestBuilder { } else if line.hasPrefix("#EXT-X-STREAM-INF") { // #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000 + // #EXT-X-STREAM-INF:BANDWIDTH=200000 currentMediaPlaylist = MediaPlaylist() - do { - let programIdString = try line.replace("(.*)=(\\d+),(.*)", replacement: "$2") - let bandwidthString = try line.replace("(.*),(.*)=(\\d+)(.*)", replacement: "$3") - if let currentMediaPlaylistExist = currentMediaPlaylist { + if let currentMediaPlaylistExist = currentMediaPlaylist { + do { + let programIdString = try line.replace("(.*)PROGRAM-ID=(\\d+)(.*)", replacement: "$2") currentMediaPlaylistExist.programId = Int(programIdString)! + } catch { + print("Failed to parse program-id on master playlist. Line = \(line)") + } + do { + let bandwidthString = try line.replace("(.*)BANDWIDTH=(\\d+)(.*)", replacement: "$2") currentMediaPlaylistExist.bandwidth = Int(bandwidthString)! + } catch { + print("Failed to parse bandwidth on master playlist. Line = \(line)") } - } catch { - print("Failed to parse program-id and bandwidth on master playlist. Line = \(line)") } } diff --git a/sources/StringExtensions.swift b/sources/StringExtensions.swift index 87f925f..fb5ccbe 100644 --- a/sources/StringExtensions.swift +++ b/sources/StringExtensions.swift @@ -12,11 +12,17 @@ extension String { func replace(_ pattern: String, replacement: String) throws -> String { let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) - + let options: NSRegularExpression.MatchingOptions = [.withTransparentBounds] + let range = NSRange(location: 0, length: self.characters.count) + let matches = regex.matches(in: self, options: options, range: range) + guard matches.count > 0 else { + throw NSError(domain: "Pantomime", code: 123, userInfo: ["description": "Couldn't find matches with RegEx"]) + } + return regex.stringByReplacingMatches( in: self, - options: [.withTransparentBounds], - range: NSRange(location: 0, length: self.characters.count), + options: options, + range: range, withTemplate: replacement ) }