diff --git a/Pantomime.xcodeproj/project.pbxproj b/Pantomime.xcodeproj/project.pbxproj index 2236fe4..17755ab 100755 --- a/Pantomime.xcodeproj/project.pbxproj +++ b/Pantomime.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 27EDFCB21E9061B400945246 /* media3.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = 27EDFCB11E9061B400945246 /* media3.m3u8 */; }; 5ED0327E1D6F1710006DE1F3 /* ManifestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED032791D6F1710006DE1F3 /* ManifestBuilder.swift */; }; 5ED0327F1D6F1710006DE1F3 /* MediaSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327A1D6F1710006DE1F3 /* MediaSegment.swift */; }; 5ED032801D6F1710006DE1F3 /* MediaPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327B1D6F1710006DE1F3 /* MediaPlaylist.swift */; }; @@ -69,6 +70,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 27EDFCB11E9061B400945246 /* media3.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = media3.m3u8; sourceTree = ""; }; 5ED032791D6F1710006DE1F3 /* ManifestBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ManifestBuilder.swift; path = sources/ManifestBuilder.swift; sourceTree = ""; }; 5ED0327A1D6F1710006DE1F3 /* MediaSegment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaSegment.swift; path = sources/MediaSegment.swift; sourceTree = ""; }; 5ED0327B1D6F1710006DE1F3 /* MediaPlaylist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaPlaylist.swift; path = sources/MediaPlaylist.swift; sourceTree = ""; }; @@ -188,6 +190,7 @@ 9EDCE3F31C09D211002FA4A7 /* PantomimeTests.swift */, 7FBCA10A0EB4EAA0CD4AB5EF /* ReaderTests.swift */, 7FBCAFC77C9DA4C80FAEA0C2 /* PlaylistTests.swift */, + 27EDFCB11E9061B400945246 /* media3.m3u8 */, ); path = PantomimeTests; sourceTree = ""; @@ -354,6 +357,7 @@ buildActionMask = 2147483647; files = ( 5ED0328F1D6F17BF006DE1F3 /* media2.m3u8 in Resources */, + 27EDFCB21E9061B400945246 /* media3.m3u8 in Resources */, 5ED032961D6F7618006DE1F3 /* master.m3u8 in Resources */, 5ED032901D6F17BF006DE1F3 /* media.m3u8 in Resources */, ); diff --git a/PantomimeTests/PantomimeTests.swift b/PantomimeTests/PantomimeTests.swift index b0638fd..04dee73 100644 --- a/PantomimeTests/PantomimeTests.swift +++ b/PantomimeTests/PantomimeTests.swift @@ -10,6 +10,18 @@ import XCTest @testable import Pantomime class PantomimeTests: XCTestCase { + + func test3ParseMediaPlaylist() { + let bundle = Bundle(for: type(of: self)) + let path = bundle.path(forResource: "media3", ofType: "m3u8")! + + let manifestBuilder = ManifestBuilder() + let mediaPlaylist = manifestBuilder.parseMediaPlaylistFromFile(path) + + XCTAssert(mediaPlaylist.segments.count == 1) + XCTAssert(mediaPlaylist.segments[0].title == "Hey this is working!") + XCTAssert(mediaPlaylist.segments[0].properties?["tvg-name"] == "example") + } func testParseMediaPlaylist() { let bundle = Bundle(for: type(of: self)) @@ -23,7 +35,7 @@ class PantomimeTests: XCTestCase { XCTAssert(mediaPlaylist.targetDuration == 10) XCTAssert(mediaPlaylist.mediaSequence == 0) - XCTAssert(mediaPlaylist.segments.count == 3) + XCTAssert(mediaPlaylist.segments.count == 4) XCTAssert(mediaPlaylist.segments[0].title == " no desc") XCTAssert(mediaPlaylist.segments[0].subrangeLength == 100) XCTAssert(mediaPlaylist.segments[0].subrangeStart == 40) diff --git a/PantomimeTests/ReaderTests.swift b/PantomimeTests/ReaderTests.swift index c99b7fc..8496a30 100644 --- a/PantomimeTests/ReaderTests.swift +++ b/PantomimeTests/ReaderTests.swift @@ -25,6 +25,8 @@ class ReaderTests: XCTestCase { for _ in 1...10 { _ = fileReader.readLine()! } + XCTAssertEqual("#EXTINF:-1 tvg-ID=\"\" tvg-name=\"example\" custom-attribute=\"\" group-title=\"another example\",title: of channel", fileReader.readLine()) + XCTAssertEqual("http://media.example.com/fourth.ts", fileReader.readLine()) XCTAssertNil(fileReader.readLine()) let httpReader = try ReaderBuilder.createReader(.httpreader, reference: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") diff --git a/PantomimeTests/media.m3u8 b/PantomimeTests/media.m3u8 index b2a7a68..fa18c88 100644 --- a/PantomimeTests/media.m3u8 +++ b/PantomimeTests/media.m3u8 @@ -9,4 +9,6 @@ http://media.example.com/first.ts #EXT-X-BYTERANGE:100 http://media.example.com/second.ts #EXTINF:3.003, -http://media.example.com/third.ts \ No newline at end of file +http://media.example.com/third.ts +#EXTINF:-1 tvg-ID="" tvg-name="example" custom-attribute="" group-title="another example",title: of channel +http://media.example.com/fourth.ts diff --git a/PantomimeTests/media3.m3u8 b/PantomimeTests/media3.m3u8 new file mode 100644 index 0000000..55b2030 --- /dev/null +++ b/PantomimeTests/media3.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#This is a comment +#EXTINF:-1 tvg-ID="" tvg-name="example" custom-attribute="" group-title="another example",Hey this is working! +http://media.example.com/fourth.ts +#EXTINF:-1 tvg-ID="" tvg-name="Kingsman: The Secret Service" tvg-logo="https://images-na.ssl-images-amazon.com/images/M/MV5BMTkxMjgwMDM4Ml5BMl5BanBnXkFtZTgwMTk3NTIwNDE@._V1_SY1000_CR0,0,675,1000_AL_.jpg" group-title="VOD: Action",Kingsman: The Secret Service +http://client-proiptv.com:8080/movie/AwC7WSEgSy/C1IxqxuT2k/9745.mkv diff --git a/sources/ManifestBuilder.swift b/sources/ManifestBuilder.swift index bc12e09..9847460 100644 --- a/sources/ManifestBuilder.swift +++ b/sources/ManifestBuilder.swift @@ -121,8 +121,12 @@ open class ManifestBuilder { } else if line.hasPrefix("#EXTINF") { currentSegment = MediaSegment() do { - let segmentDurationString = try line.replace("(.*):(\\d.*),(.*)", replacement: "$2") - let segmentTitle = try line.replace("(.*):(\\d.*),(.*)", replacement: "$3") + let generalRegex = "(.*):(-?[0-9]\\d*(\\.\\d+)?)(.*),(.*)" + let segmentDurationString = try line.replace(generalRegex, replacement: "$2") + let rawProperties = try line.replace(generalRegex, replacement: "$4") + let segmentTitle = try line.replace(generalRegex, replacement: "$5") + + currentSegment!.properties = getProperties(in: rawProperties) currentSegment!.duration = Float(segmentDurationString) currentSegment!.title = segmentTitle } catch { @@ -172,6 +176,25 @@ open class ManifestBuilder { return mediaPlaylist } + func getProperties(in text: String) -> [String:String]? { + do { + let regex = try NSRegularExpression(pattern: "([a-zA-z-]*)=\"(.*?)\"") + let nsString = text as NSString + let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length)) + let keys = results.map { nsString.substring(with: $0.rangeAt(1))} + let values = results.map { nsString.substring(with: $0.rangeAt(2))} + var result = [String:String]() + for (index, value) in values.enumerated() { + result[keys[index]] = value + } + return result +// return results.map { nsString.substring(with: $0.range)} + } catch let error { + print("Error: \(error.localizedDescription)") + return nil + } + } + /** * Parses the master playlist manifest from a string document. * diff --git a/sources/MediaPlaylist.swift b/sources/MediaPlaylist.swift index 7c884c8..7ed3200 100644 --- a/sources/MediaPlaylist.swift +++ b/sources/MediaPlaylist.swift @@ -38,6 +38,9 @@ open class MediaPlaylist { open func duration() -> Float { var dur: Float = 0.0 for item in segments { + if item.duration == nil || item.duration! <= 0 { + continue + } dur = dur + item.duration! } return dur diff --git a/sources/MediaSegment.swift b/sources/MediaSegment.swift index d5e3af0..f1a8184 100644 --- a/sources/MediaSegment.swift +++ b/sources/MediaSegment.swift @@ -14,6 +14,7 @@ open class MediaSegment { open var title: String? open var discontinuity: Bool = false open var path: String? + open var properties: [String:String]? public init() {