├── .gitignore ├── .swiftlint.yml ├── .travis.yml ├── CHANGELOG.md ├── Info.plist ├── LICENSE ├── Package.swift ├── Pantomime.podspec ├── Pantomime.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Pantomime (OSX).xcscheme │ ├── Pantomime (iOS).xcscheme │ └── Pantomime (tvOS).xcscheme ├── PantomimeTests ├── Info.plist ├── PantomimeTests.swift ├── PlaylistTests.swift ├── ReaderTests.swift ├── master.m3u8 ├── media.m3u8 └── media2.m3u8 ├── README.md └── sources ├── BufferedReader.swift ├── FileBufferedReader.swift ├── ManifestBuilder.swift ├── MasterPlaylist.swift ├── MediaPlaylist.swift ├── MediaSegment.swift ├── NSURLExtension.swift ├── ReaderBuilder.swift ├── StreamReader.swift ├── StringBufferedReader.swift ├── StringExtensions.swift └── URLBufferedReader.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # for Xcode 2 | ########### 3 | 4 | .DS_Store 5 | .DS_Store? 6 | Icon? 7 | 8 | Secrets.* 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | .idea 25 | 26 | ## Other 27 | *.xccheckout 28 | *.moved-aside 29 | *.xcuserstate 30 | *.xcscmblueprint 31 | 32 | ## Obj-C/Swift specific 33 | *.hmap 34 | *.ipa 35 | 36 | ## Playgrounds 37 | timeline.xctimeline 38 | playground.xcworkspace 39 | 40 | # Swift Package Manager 41 | # 42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 43 | # Packages/ 44 | .build/ 45 | 46 | # CocoaPods 47 | # 48 | # We recommend against adding the Pods directory to your .gitignore. However 49 | # you should judge for yourself, the pros and cons are mentioned at: 50 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 51 | # 52 | # Pods/ 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/ 60 | 61 | # fastlane 62 | # 63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 64 | # screenshots whenever they are needed. 65 | # For more information about the recommended setup visit: 66 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 67 | 68 | fastlane/report.xml 69 | fastlane/screenshots 70 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/realm/SwiftLint 2 | # 3 | # run on CLI (or as build phase): 4 | # 5 | # swiftlint rules / autocorrect / lint (default) 6 | # 7 | # 8 | 9 | disabled_rules: # rule identifiers to exclude from running 10 | - valid_docs # /// is valid and used by Xcode 11 | - cyclomatic_complexity 12 | - variable_name 13 | 14 | opt_in_rules: # some rules are only opt-in 15 | - empty_count 16 | #- missing_docs 17 | 18 | # Find all the available rules by running: 19 | # swiftlint rules 20 | 21 | included: # paths to include during linting. `--path` is ignored if present. 22 | - sources 23 | - SwiftyBeaver 24 | - SwiftyBeaverTests 25 | 26 | excluded: # paths to ignore during linting. Takes precedence over `included`. 27 | - Carthage 28 | 29 | line_length: 121 30 | 31 | function_parameter_count: 32 | warning: 10 33 | error: 15 34 | 35 | function_body_length: 36 | warning: 80 37 | error: 100 38 | 39 | type_body_length: 40 | warning: 700 41 | error: 1000 42 | 43 | file_length: 44 | warning: 1200 45 | error: 1500 46 | 47 | variable_name: 48 | min_length: 2 49 | excluded: 50 | - translatesAutoresizingMaskIntoConstraints 51 | 52 | type_name: 53 | min_length: 2 54 | 55 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle) 56 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: Pantomime.xcodeproj 3 | xcode_scheme: Pantomime (iOS) 4 | osx_image: xcode7.3 5 | xcode_sdk: iphonesimulator9.3 6 | before_install: 7 | - gem install xcpretty 8 | script: 9 | - set -o pipefail # required to let xcpretty use the same exit code like xcodebuild 10 | - xcodebuild -project Pantomime.xcodeproj -scheme "Pantomime (iOS)" -destination name="iPhone 6" -destination name="iPhone 6" test | xcpretty -c 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file following the style described at [Keep a Changelog](http://keepachangelog.com) by [@olivierlacan](https://github.com/olivierlacan). 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ---- 7 | ## 0.1.4 (2016-11-21) 8 | 9 | ##### Fixed 10 | 11 | - "fatal error: Index out of range" on getSegment and getPlaylist. 12 | 13 |
14 | 15 | ## 0.1.3 (2016-08-29) 16 | 17 | ##### Fixed 18 | 19 | - "fatal error: Index out of range" on getSegment and getPlaylist. 20 | 21 |
22 | 23 | ## 0.1.2 (2016-08-29) 24 | 25 | ##### Fixed 26 | 27 | - public constructors, properties and methods added 28 |
29 | 30 | ## 0.1.1 (2016-08-28) 31 | 32 | ##### Updated 33 | 34 | - Docs (readme) 35 | 36 | ##### Fixed 37 | 38 | - public constructor was missing in ManifestBuilder 39 |
40 | 41 | ## 0.1.0 (2016-08-28) 42 | 43 | ##### Added 44 | 45 | - The first release of the HLS Manifest parser 46 | - Reads and parses MediaPlaylist and MediaSegments 47 | - Can read full manifest with loading of referrenced manifests 48 |
49 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.1.4 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Thomas Christensen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Pantomime" 5 | ) 6 | -------------------------------------------------------------------------------- /Pantomime.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Pantomime" 3 | s.version = "0.1.4" 4 | s.summary = "Parsing of M3U8 manifest files for Swift" 5 | 6 | s.description = <<-DESC 7 | M3U8Parser4Swift reads and writes HTTP Live Streaming manifest files. 8 | Use it to fetch a Master manifest and for parsing it. Supports the 9 | Internet-Draft version 7. Can be used to throw events when various elements 10 | have been parsed. Use it to contruct a new manifest from scratch. 11 | Supports Master and Media playlist manifest files. 12 | DESC 13 | 14 | s.homepage = "https://github.com/thomaschristensen/Pantomime" 15 | s.license = "MIT" 16 | s.author = { "Thomas Christensen" => "tchristensen@nordija.com" } 17 | s.ios.deployment_target = "8.0" 18 | s.tvos.deployment_target = "9.0" 19 | s.osx.deployment_target = "10.10" 20 | s.source = { :git => "https://github.com/thomaschristensen/Pantomime.git", :tag => "0.1.4" } 21 | s.source_files = "sources" 22 | end 23 | -------------------------------------------------------------------------------- /Pantomime.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5ED0327E1D6F1710006DE1F3 /* ManifestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED032791D6F1710006DE1F3 /* ManifestBuilder.swift */; }; 11 | 5ED0327F1D6F1710006DE1F3 /* MediaSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327A1D6F1710006DE1F3 /* MediaSegment.swift */; }; 12 | 5ED032801D6F1710006DE1F3 /* MediaPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327B1D6F1710006DE1F3 /* MediaPlaylist.swift */; }; 13 | 5ED032811D6F1710006DE1F3 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327C1D6F1710006DE1F3 /* StringExtensions.swift */; }; 14 | 5ED032821D6F1710006DE1F3 /* StreamReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327D1D6F1710006DE1F3 /* StreamReader.swift */; }; 15 | 5ED032831D6F1721006DE1F3 /* ManifestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED032791D6F1710006DE1F3 /* ManifestBuilder.swift */; }; 16 | 5ED032841D6F1722006DE1F3 /* ManifestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED032791D6F1710006DE1F3 /* ManifestBuilder.swift */; }; 17 | 5ED032851D6F1728006DE1F3 /* MediaSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327A1D6F1710006DE1F3 /* MediaSegment.swift */; }; 18 | 5ED032861D6F1728006DE1F3 /* MediaSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327A1D6F1710006DE1F3 /* MediaSegment.swift */; }; 19 | 5ED032871D6F172B006DE1F3 /* MediaPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327B1D6F1710006DE1F3 /* MediaPlaylist.swift */; }; 20 | 5ED032881D6F172C006DE1F3 /* MediaPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327B1D6F1710006DE1F3 /* MediaPlaylist.swift */; }; 21 | 5ED032891D6F1731006DE1F3 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327C1D6F1710006DE1F3 /* StringExtensions.swift */; }; 22 | 5ED0328A1D6F1731006DE1F3 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327C1D6F1710006DE1F3 /* StringExtensions.swift */; }; 23 | 5ED0328B1D6F1734006DE1F3 /* StreamReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327D1D6F1710006DE1F3 /* StreamReader.swift */; }; 24 | 5ED0328C1D6F1735006DE1F3 /* StreamReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED0327D1D6F1710006DE1F3 /* StreamReader.swift */; }; 25 | 5ED0328F1D6F17BF006DE1F3 /* media2.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = 5ED0328D1D6F17BF006DE1F3 /* media2.m3u8 */; }; 26 | 5ED032901D6F17BF006DE1F3 /* media.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = 5ED0328E1D6F17BF006DE1F3 /* media.m3u8 */; }; 27 | 5ED032921D6F7539006DE1F3 /* MasterPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED032911D6F7539006DE1F3 /* MasterPlaylist.swift */; }; 28 | 5ED032931D6F7539006DE1F3 /* MasterPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED032911D6F7539006DE1F3 /* MasterPlaylist.swift */; }; 29 | 5ED032941D6F7539006DE1F3 /* MasterPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED032911D6F7539006DE1F3 /* MasterPlaylist.swift */; }; 30 | 5ED032961D6F7618006DE1F3 /* master.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = 5ED032951D6F7618006DE1F3 /* master.m3u8 */; }; 31 | 7FBCA08CA592BD0DC04B8FE5 /* StringBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA22C61E0D299DF542F4B /* StringBufferedReader.swift */; }; 32 | 7FBCA092389AE5D6DF3A72EB /* ReaderBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAFFB2E9DF6E75D5AF46D /* ReaderBuilder.swift */; }; 33 | 7FBCA48C54BDA0AB70B73393 /* FileBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA03857771FD1265B089F /* FileBufferedReader.swift */; }; 34 | 7FBCA552A6090C2DF269D9E9 /* NSURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA9CB6FB8AA516DB9C538 /* NSURLExtension.swift */; }; 35 | 7FBCA65DDE8C32438FE4E9C8 /* FileBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA03857771FD1265B089F /* FileBufferedReader.swift */; }; 36 | 7FBCA6A7E5981681AB2E5E48 /* ReaderBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAFFB2E9DF6E75D5AF46D /* ReaderBuilder.swift */; }; 37 | 7FBCA81E63E9441F3AC22BB7 /* ReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA10A0EB4EAA0CD4AB5EF /* ReaderTests.swift */; }; 38 | 7FBCA8DD1928C9CD881780F6 /* BufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAAE76466B9E53DA130F3 /* BufferedReader.swift */; }; 39 | 7FBCAB4AB49726BF5F8EB6C8 /* BufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAAE76466B9E53DA130F3 /* BufferedReader.swift */; }; 40 | 7FBCABD199BB1B8440EC750E /* FileBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA03857771FD1265B089F /* FileBufferedReader.swift */; }; 41 | 7FBCABE9DE7114831052FA9D /* BufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAAE76466B9E53DA130F3 /* BufferedReader.swift */; }; 42 | 7FBCAC77789A6B78BFB3B2BE /* ReaderBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAFFB2E9DF6E75D5AF46D /* ReaderBuilder.swift */; }; 43 | 7FBCACF2178BC111C07423B5 /* URLBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAAA79D01BF5CEBA7261A /* URLBufferedReader.swift */; }; 44 | 7FBCAD486D18CBDBCA89C3F6 /* NSURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA9CB6FB8AA516DB9C538 /* NSURLExtension.swift */; }; 45 | 7FBCAD4D0E61F2913881E262 /* PlaylistTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAFC77C9DA4C80FAEA0C2 /* PlaylistTests.swift */; }; 46 | 7FBCAD932FF257481CDB95DE /* StringBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA22C61E0D299DF542F4B /* StringBufferedReader.swift */; }; 47 | 7FBCADB79746F44D9AC5F47C /* URLBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAAA79D01BF5CEBA7261A /* URLBufferedReader.swift */; }; 48 | 7FBCAE6AB452BA202FB48490 /* NSURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA9CB6FB8AA516DB9C538 /* NSURLExtension.swift */; }; 49 | 7FBCAEE343068A84C3A08C26 /* StringBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCA22C61E0D299DF542F4B /* StringBufferedReader.swift */; }; 50 | 7FBCAF63D24C6183A51D09D8 /* URLBufferedReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBCAAA79D01BF5CEBA7261A /* URLBufferedReader.swift */; }; 51 | 9E6CAA481C148EC1009D9093 /* Pantomime.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 9E6CAA471C148EC1009D9093 /* Pantomime.podspec */; }; 52 | 9E6CAA4A1C149579009D9093 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9E6CAA491C149579009D9093 /* LICENSE */; }; 53 | 9EDCE3EF1C09D211002FA4A7 /* Pantomime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EDCE3E41C09D211002FA4A7 /* Pantomime.framework */; }; 54 | 9EDCE3F41C09D211002FA4A7 /* PantomimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDCE3F31C09D211002FA4A7 /* PantomimeTests.swift */; }; 55 | ACE5978F1C18830D0031451F /* Pantomime.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 9E6CAA471C148EC1009D9093 /* Pantomime.podspec */; }; 56 | ACE597901C18830D0031451F /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9E6CAA491C149579009D9093 /* LICENSE */; }; 57 | ACE597A01C18832E0031451F /* Pantomime.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 9E6CAA471C148EC1009D9093 /* Pantomime.podspec */; }; 58 | ACE597A11C18832E0031451F /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9E6CAA491C149579009D9093 /* LICENSE */; }; 59 | /* End PBXBuildFile section */ 60 | 61 | /* Begin PBXContainerItemProxy section */ 62 | 9EDCE3F01C09D211002FA4A7 /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = 9EDCE3DB1C09D211002FA4A7 /* Project object */; 65 | proxyType = 1; 66 | remoteGlobalIDString = 9EDCE3E31C09D211002FA4A7; 67 | remoteInfo = Pantomime; 68 | }; 69 | /* End PBXContainerItemProxy section */ 70 | 71 | /* Begin PBXFileReference section */ 72 | 5ED032791D6F1710006DE1F3 /* ManifestBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ManifestBuilder.swift; path = sources/ManifestBuilder.swift; sourceTree = ""; }; 73 | 5ED0327A1D6F1710006DE1F3 /* MediaSegment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaSegment.swift; path = sources/MediaSegment.swift; sourceTree = ""; }; 74 | 5ED0327B1D6F1710006DE1F3 /* MediaPlaylist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MediaPlaylist.swift; path = sources/MediaPlaylist.swift; sourceTree = ""; }; 75 | 5ED0327C1D6F1710006DE1F3 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringExtensions.swift; path = sources/StringExtensions.swift; sourceTree = ""; }; 76 | 5ED0327D1D6F1710006DE1F3 /* StreamReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StreamReader.swift; path = sources/StreamReader.swift; sourceTree = ""; }; 77 | 5ED0328D1D6F17BF006DE1F3 /* media2.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = media2.m3u8; sourceTree = ""; }; 78 | 5ED0328E1D6F17BF006DE1F3 /* media.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = media.m3u8; sourceTree = ""; }; 79 | 5ED032911D6F7539006DE1F3 /* MasterPlaylist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MasterPlaylist.swift; path = sources/MasterPlaylist.swift; sourceTree = ""; }; 80 | 5ED032951D6F7618006DE1F3 /* master.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = master.m3u8; sourceTree = ""; }; 81 | 7FBCA03857771FD1265B089F /* FileBufferedReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FileBufferedReader.swift; path = sources/FileBufferedReader.swift; sourceTree = ""; }; 82 | 7FBCA10A0EB4EAA0CD4AB5EF /* ReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReaderTests.swift; sourceTree = ""; }; 83 | 7FBCA22C61E0D299DF542F4B /* StringBufferedReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringBufferedReader.swift; path = sources/StringBufferedReader.swift; sourceTree = ""; }; 84 | 7FBCA9CB6FB8AA516DB9C538 /* NSURLExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NSURLExtension.swift; path = sources/NSURLExtension.swift; sourceTree = ""; }; 85 | 7FBCAAA79D01BF5CEBA7261A /* URLBufferedReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLBufferedReader.swift; path = sources/URLBufferedReader.swift; sourceTree = ""; }; 86 | 7FBCAAE76466B9E53DA130F3 /* BufferedReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BufferedReader.swift; path = sources/BufferedReader.swift; sourceTree = ""; }; 87 | 7FBCAFC77C9DA4C80FAEA0C2 /* PlaylistTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaylistTests.swift; sourceTree = ""; }; 88 | 7FBCAFFB2E9DF6E75D5AF46D /* ReaderBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ReaderBuilder.swift; path = sources/ReaderBuilder.swift; sourceTree = ""; }; 89 | 9E195B101C6B766D00D924CB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 90 | 9E4BBDFA1C09EB7F00220716 /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 91 | 9E4BBDFE1C0A01D000220716 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 92 | 9E6CAA471C148EC1009D9093 /* Pantomime.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Pantomime.podspec; sourceTree = ""; }; 93 | 9E6CAA491C149579009D9093 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 94 | 9E74FEA81CD74AFA0024DD76 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 95 | 9EDCE3E41C09D211002FA4A7 /* Pantomime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pantomime.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 96 | 9EDCE3EE1C09D211002FA4A7 /* PantomimeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PantomimeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 97 | 9EDCE3F31C09D211002FA4A7 /* PantomimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PantomimeTests.swift; sourceTree = ""; }; 98 | 9EDCE3F51C09D211002FA4A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 99 | ACE597951C18830D0031451F /* Pantomime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pantomime.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 100 | ACE597A61C18832E0031451F /* Pantomime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pantomime.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 101 | /* End PBXFileReference section */ 102 | 103 | /* Begin PBXFrameworksBuildPhase section */ 104 | 9EDCE3E01C09D211002FA4A7 /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | 9EDCE3EB1C09D211002FA4A7 /* Frameworks */ = { 112 | isa = PBXFrameworksBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | 9EDCE3EF1C09D211002FA4A7 /* Pantomime.framework in Frameworks */, 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | ACE5978C1C18830D0031451F /* Frameworks */ = { 120 | isa = PBXFrameworksBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | ACE5979D1C18832E0031451F /* Frameworks */ = { 127 | isa = PBXFrameworksBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXFrameworksBuildPhase section */ 134 | 135 | /* Begin PBXGroup section */ 136 | 9E31D62A1C185ED0004E0980 /* sources */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 5ED032791D6F1710006DE1F3 /* ManifestBuilder.swift */, 140 | 5ED0327A1D6F1710006DE1F3 /* MediaSegment.swift */, 141 | 5ED0327B1D6F1710006DE1F3 /* MediaPlaylist.swift */, 142 | 5ED0327C1D6F1710006DE1F3 /* StringExtensions.swift */, 143 | 5ED0327D1D6F1710006DE1F3 /* StreamReader.swift */, 144 | 5ED032911D6F7539006DE1F3 /* MasterPlaylist.swift */, 145 | 7FBCAAE76466B9E53DA130F3 /* BufferedReader.swift */, 146 | 7FBCA03857771FD1265B089F /* FileBufferedReader.swift */, 147 | 7FBCAFFB2E9DF6E75D5AF46D /* ReaderBuilder.swift */, 148 | 7FBCA22C61E0D299DF542F4B /* StringBufferedReader.swift */, 149 | 7FBCAAA79D01BF5CEBA7261A /* URLBufferedReader.swift */, 150 | 7FBCA9CB6FB8AA516DB9C538 /* NSURLExtension.swift */, 151 | ); 152 | name = sources; 153 | sourceTree = ""; 154 | }; 155 | 9EDCE3DA1C09D211002FA4A7 = { 156 | isa = PBXGroup; 157 | children = ( 158 | 9E4BBDFA1C09EB7F00220716 /* .gitignore */, 159 | 9E6CAA491C149579009D9093 /* LICENSE */, 160 | 9E195B101C6B766D00D924CB /* Info.plist */, 161 | 9E74FEA81CD74AFA0024DD76 /* CHANGELOG.md */, 162 | 9E4BBDFE1C0A01D000220716 /* README.md */, 163 | 9E6CAA471C148EC1009D9093 /* Pantomime.podspec */, 164 | 9E31D62A1C185ED0004E0980 /* sources */, 165 | 9EDCE3F21C09D211002FA4A7 /* PantomimeTests */, 166 | 9EDCE3E51C09D211002FA4A7 /* Products */, 167 | ); 168 | sourceTree = ""; 169 | }; 170 | 9EDCE3E51C09D211002FA4A7 /* Products */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 9EDCE3E41C09D211002FA4A7 /* Pantomime.framework */, 174 | 9EDCE3EE1C09D211002FA4A7 /* PantomimeTests.xctest */, 175 | ACE597951C18830D0031451F /* Pantomime.framework */, 176 | ACE597A61C18832E0031451F /* Pantomime.framework */, 177 | ); 178 | name = Products; 179 | sourceTree = ""; 180 | }; 181 | 9EDCE3F21C09D211002FA4A7 /* PantomimeTests */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 5ED032951D6F7618006DE1F3 /* master.m3u8 */, 185 | 5ED0328D1D6F17BF006DE1F3 /* media2.m3u8 */, 186 | 5ED0328E1D6F17BF006DE1F3 /* media.m3u8 */, 187 | 9EDCE3F51C09D211002FA4A7 /* Info.plist */, 188 | 9EDCE3F31C09D211002FA4A7 /* PantomimeTests.swift */, 189 | 7FBCA10A0EB4EAA0CD4AB5EF /* ReaderTests.swift */, 190 | 7FBCAFC77C9DA4C80FAEA0C2 /* PlaylistTests.swift */, 191 | ); 192 | path = PantomimeTests; 193 | sourceTree = ""; 194 | }; 195 | /* End PBXGroup section */ 196 | 197 | /* Begin PBXHeadersBuildPhase section */ 198 | 9EDCE3E11C09D211002FA4A7 /* Headers */ = { 199 | isa = PBXHeadersBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | ACE5978D1C18830D0031451F /* Headers */ = { 206 | isa = PBXHeadersBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | ACE5979E1C18832E0031451F /* Headers */ = { 213 | isa = PBXHeadersBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXHeadersBuildPhase section */ 220 | 221 | /* Begin PBXNativeTarget section */ 222 | 9EDCE3E31C09D211002FA4A7 /* Pantomime (iOS) */ = { 223 | isa = PBXNativeTarget; 224 | buildConfigurationList = 9EDCE3F81C09D211002FA4A7 /* Build configuration list for PBXNativeTarget "Pantomime (iOS)" */; 225 | buildPhases = ( 226 | 5ED032971D6F82B8006DE1F3 /* ShellScript */, 227 | 9EDCE3DF1C09D211002FA4A7 /* Sources */, 228 | 9EDCE3E01C09D211002FA4A7 /* Frameworks */, 229 | 9EDCE3E11C09D211002FA4A7 /* Headers */, 230 | 9EDCE3E21C09D211002FA4A7 /* Resources */, 231 | ); 232 | buildRules = ( 233 | ); 234 | dependencies = ( 235 | ); 236 | name = "Pantomime (iOS)"; 237 | productName = Pantomime; 238 | productReference = 9EDCE3E41C09D211002FA4A7 /* Pantomime.framework */; 239 | productType = "com.apple.product-type.framework"; 240 | }; 241 | 9EDCE3ED1C09D211002FA4A7 /* PantomimeTests */ = { 242 | isa = PBXNativeTarget; 243 | buildConfigurationList = 9EDCE3FB1C09D211002FA4A7 /* Build configuration list for PBXNativeTarget "PantomimeTests" */; 244 | buildPhases = ( 245 | 9EDCE3EA1C09D211002FA4A7 /* Sources */, 246 | 9EDCE3EB1C09D211002FA4A7 /* Frameworks */, 247 | 9EDCE3EC1C09D211002FA4A7 /* Resources */, 248 | 9EB9C85B1C92C271005C0541 /* ShellScript */, 249 | ); 250 | buildRules = ( 251 | ); 252 | dependencies = ( 253 | 9EDCE3F11C09D211002FA4A7 /* PBXTargetDependency */, 254 | ); 255 | name = PantomimeTests; 256 | productName = PantomimeTests; 257 | productReference = 9EDCE3EE1C09D211002FA4A7 /* PantomimeTests.xctest */; 258 | productType = "com.apple.product-type.bundle.unit-test"; 259 | }; 260 | ACE597861C18830D0031451F /* Pantomime (tvOS) */ = { 261 | isa = PBXNativeTarget; 262 | buildConfigurationList = ACE597921C18830D0031451F /* Build configuration list for PBXNativeTarget "Pantomime (tvOS)" */; 263 | buildPhases = ( 264 | ACE597871C18830D0031451F /* Sources */, 265 | ACE5978C1C18830D0031451F /* Frameworks */, 266 | ACE5978D1C18830D0031451F /* Headers */, 267 | ACE5978E1C18830D0031451F /* Resources */, 268 | ); 269 | buildRules = ( 270 | ); 271 | dependencies = ( 272 | ); 273 | name = "Pantomime (tvOS)"; 274 | productName = Pantomime; 275 | productReference = ACE597951C18830D0031451F /* Pantomime.framework */; 276 | productType = "com.apple.product-type.framework"; 277 | }; 278 | ACE597971C18832E0031451F /* Pantomime (OSX) */ = { 279 | isa = PBXNativeTarget; 280 | buildConfigurationList = ACE597A31C18832E0031451F /* Build configuration list for PBXNativeTarget "Pantomime (OSX)" */; 281 | buildPhases = ( 282 | ACE597981C18832E0031451F /* Sources */, 283 | ACE5979D1C18832E0031451F /* Frameworks */, 284 | ACE5979E1C18832E0031451F /* Headers */, 285 | ACE5979F1C18832E0031451F /* Resources */, 286 | ); 287 | buildRules = ( 288 | ); 289 | dependencies = ( 290 | ); 291 | name = "Pantomime (OSX)"; 292 | productName = Pantomime; 293 | productReference = ACE597A61C18832E0031451F /* Pantomime.framework */; 294 | productType = "com.apple.product-type.framework"; 295 | }; 296 | /* End PBXNativeTarget section */ 297 | 298 | /* Begin PBXProject section */ 299 | 9EDCE3DB1C09D211002FA4A7 /* Project object */ = { 300 | isa = PBXProject; 301 | attributes = { 302 | LastSwiftUpdateCheck = 0730; 303 | LastUpgradeCheck = 1000; 304 | ORGANIZATIONNAME = "Thomas Christensen"; 305 | TargetAttributes = { 306 | 9EDCE3E31C09D211002FA4A7 = { 307 | CreatedOnToolsVersion = 7.1.1; 308 | DevelopmentTeam = W9ZVWEMJZF; 309 | LastSwiftMigration = 1000; 310 | }; 311 | 9EDCE3ED1C09D211002FA4A7 = { 312 | CreatedOnToolsVersion = 7.1.1; 313 | DevelopmentTeam = W9ZVWEMJZF; 314 | LastSwiftMigration = 1000; 315 | }; 316 | ACE597861C18830D0031451F = { 317 | DevelopmentTeam = W9ZVWEMJZF; 318 | LastSwiftMigration = 0800; 319 | }; 320 | ACE597971C18832E0031451F = { 321 | DevelopmentTeam = W9ZVWEMJZF; 322 | LastSwiftMigration = 0800; 323 | ProvisioningStyle = Automatic; 324 | }; 325 | }; 326 | }; 327 | buildConfigurationList = 9EDCE3DE1C09D211002FA4A7 /* Build configuration list for PBXProject "Pantomime" */; 328 | compatibilityVersion = "Xcode 3.2"; 329 | developmentRegion = English; 330 | hasScannedForEncodings = 0; 331 | knownRegions = ( 332 | en, 333 | ); 334 | mainGroup = 9EDCE3DA1C09D211002FA4A7; 335 | productRefGroup = 9EDCE3E51C09D211002FA4A7 /* Products */; 336 | projectDirPath = ""; 337 | projectRoot = ""; 338 | targets = ( 339 | 9EDCE3E31C09D211002FA4A7 /* Pantomime (iOS) */, 340 | 9EDCE3ED1C09D211002FA4A7 /* PantomimeTests */, 341 | ACE597861C18830D0031451F /* Pantomime (tvOS) */, 342 | ACE597971C18832E0031451F /* Pantomime (OSX) */, 343 | ); 344 | }; 345 | /* End PBXProject section */ 346 | 347 | /* Begin PBXResourcesBuildPhase section */ 348 | 9EDCE3E21C09D211002FA4A7 /* Resources */ = { 349 | isa = PBXResourcesBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | 9E6CAA481C148EC1009D9093 /* Pantomime.podspec in Resources */, 353 | 9E6CAA4A1C149579009D9093 /* LICENSE in Resources */, 354 | ); 355 | runOnlyForDeploymentPostprocessing = 0; 356 | }; 357 | 9EDCE3EC1C09D211002FA4A7 /* Resources */ = { 358 | isa = PBXResourcesBuildPhase; 359 | buildActionMask = 2147483647; 360 | files = ( 361 | 5ED0328F1D6F17BF006DE1F3 /* media2.m3u8 in Resources */, 362 | 5ED032961D6F7618006DE1F3 /* master.m3u8 in Resources */, 363 | 5ED032901D6F17BF006DE1F3 /* media.m3u8 in Resources */, 364 | ); 365 | runOnlyForDeploymentPostprocessing = 0; 366 | }; 367 | ACE5978E1C18830D0031451F /* Resources */ = { 368 | isa = PBXResourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | ACE5978F1C18830D0031451F /* Pantomime.podspec in Resources */, 372 | ACE597901C18830D0031451F /* LICENSE in Resources */, 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | }; 376 | ACE5979F1C18832E0031451F /* Resources */ = { 377 | isa = PBXResourcesBuildPhase; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | ACE597A01C18832E0031451F /* Pantomime.podspec in Resources */, 381 | ACE597A11C18832E0031451F /* LICENSE in Resources */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | /* End PBXResourcesBuildPhase section */ 386 | 387 | /* Begin PBXShellScriptBuildPhase section */ 388 | 5ED032971D6F82B8006DE1F3 /* ShellScript */ = { 389 | isa = PBXShellScriptBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | ); 393 | inputPaths = ( 394 | ); 395 | outputPaths = ( 396 | ); 397 | runOnlyForDeploymentPostprocessing = 0; 398 | shellPath = /bin/sh; 399 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; 400 | }; 401 | 9EB9C85B1C92C271005C0541 /* ShellScript */ = { 402 | isa = PBXShellScriptBuildPhase; 403 | buildActionMask = 2147483647; 404 | files = ( 405 | ); 406 | inputPaths = ( 407 | ); 408 | outputPaths = ( 409 | ); 410 | runOnlyForDeploymentPostprocessing = 0; 411 | shellPath = /bin/sh; 412 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint autocorrect\n swiftlint\nfi"; 413 | }; 414 | /* End PBXShellScriptBuildPhase section */ 415 | 416 | /* Begin PBXSourcesBuildPhase section */ 417 | 9EDCE3DF1C09D211002FA4A7 /* Sources */ = { 418 | isa = PBXSourcesBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | 5ED032821D6F1710006DE1F3 /* StreamReader.swift in Sources */, 422 | 5ED032811D6F1710006DE1F3 /* StringExtensions.swift in Sources */, 423 | 5ED0327F1D6F1710006DE1F3 /* MediaSegment.swift in Sources */, 424 | 5ED032921D6F7539006DE1F3 /* MasterPlaylist.swift in Sources */, 425 | 5ED0327E1D6F1710006DE1F3 /* ManifestBuilder.swift in Sources */, 426 | 5ED032801D6F1710006DE1F3 /* MediaPlaylist.swift in Sources */, 427 | 7FBCABE9DE7114831052FA9D /* BufferedReader.swift in Sources */, 428 | 7FBCA48C54BDA0AB70B73393 /* FileBufferedReader.swift in Sources */, 429 | 7FBCA6A7E5981681AB2E5E48 /* ReaderBuilder.swift in Sources */, 430 | 7FBCA08CA592BD0DC04B8FE5 /* StringBufferedReader.swift in Sources */, 431 | 7FBCAF63D24C6183A51D09D8 /* URLBufferedReader.swift in Sources */, 432 | 7FBCAE6AB452BA202FB48490 /* NSURLExtension.swift in Sources */, 433 | ); 434 | runOnlyForDeploymentPostprocessing = 0; 435 | }; 436 | 9EDCE3EA1C09D211002FA4A7 /* Sources */ = { 437 | isa = PBXSourcesBuildPhase; 438 | buildActionMask = 2147483647; 439 | files = ( 440 | 9EDCE3F41C09D211002FA4A7 /* PantomimeTests.swift in Sources */, 441 | 7FBCA81E63E9441F3AC22BB7 /* ReaderTests.swift in Sources */, 442 | 7FBCAD4D0E61F2913881E262 /* PlaylistTests.swift in Sources */, 443 | ); 444 | runOnlyForDeploymentPostprocessing = 0; 445 | }; 446 | ACE597871C18830D0031451F /* Sources */ = { 447 | isa = PBXSourcesBuildPhase; 448 | buildActionMask = 2147483647; 449 | files = ( 450 | 5ED0328C1D6F1735006DE1F3 /* StreamReader.swift in Sources */, 451 | 5ED032891D6F1731006DE1F3 /* StringExtensions.swift in Sources */, 452 | 5ED032851D6F1728006DE1F3 /* MediaSegment.swift in Sources */, 453 | 5ED032931D6F7539006DE1F3 /* MasterPlaylist.swift in Sources */, 454 | 5ED032831D6F1721006DE1F3 /* ManifestBuilder.swift in Sources */, 455 | 5ED032881D6F172C006DE1F3 /* MediaPlaylist.swift in Sources */, 456 | 7FBCA8DD1928C9CD881780F6 /* BufferedReader.swift in Sources */, 457 | 7FBCABD199BB1B8440EC750E /* FileBufferedReader.swift in Sources */, 458 | 7FBCAC77789A6B78BFB3B2BE /* ReaderBuilder.swift in Sources */, 459 | 7FBCAEE343068A84C3A08C26 /* StringBufferedReader.swift in Sources */, 460 | 7FBCADB79746F44D9AC5F47C /* URLBufferedReader.swift in Sources */, 461 | 7FBCAD486D18CBDBCA89C3F6 /* NSURLExtension.swift in Sources */, 462 | ); 463 | runOnlyForDeploymentPostprocessing = 0; 464 | }; 465 | ACE597981C18832E0031451F /* Sources */ = { 466 | isa = PBXSourcesBuildPhase; 467 | buildActionMask = 2147483647; 468 | files = ( 469 | 5ED0328B1D6F1734006DE1F3 /* StreamReader.swift in Sources */, 470 | 5ED0328A1D6F1731006DE1F3 /* StringExtensions.swift in Sources */, 471 | 5ED032861D6F1728006DE1F3 /* MediaSegment.swift in Sources */, 472 | 5ED032941D6F7539006DE1F3 /* MasterPlaylist.swift in Sources */, 473 | 5ED032841D6F1722006DE1F3 /* ManifestBuilder.swift in Sources */, 474 | 5ED032871D6F172B006DE1F3 /* MediaPlaylist.swift in Sources */, 475 | 7FBCAB4AB49726BF5F8EB6C8 /* BufferedReader.swift in Sources */, 476 | 7FBCA65DDE8C32438FE4E9C8 /* FileBufferedReader.swift in Sources */, 477 | 7FBCA092389AE5D6DF3A72EB /* ReaderBuilder.swift in Sources */, 478 | 7FBCAD932FF257481CDB95DE /* StringBufferedReader.swift in Sources */, 479 | 7FBCACF2178BC111C07423B5 /* URLBufferedReader.swift in Sources */, 480 | 7FBCA552A6090C2DF269D9E9 /* NSURLExtension.swift in Sources */, 481 | ); 482 | runOnlyForDeploymentPostprocessing = 0; 483 | }; 484 | /* End PBXSourcesBuildPhase section */ 485 | 486 | /* Begin PBXTargetDependency section */ 487 | 9EDCE3F11C09D211002FA4A7 /* PBXTargetDependency */ = { 488 | isa = PBXTargetDependency; 489 | target = 9EDCE3E31C09D211002FA4A7 /* Pantomime (iOS) */; 490 | targetProxy = 9EDCE3F01C09D211002FA4A7 /* PBXContainerItemProxy */; 491 | }; 492 | /* End PBXTargetDependency section */ 493 | 494 | /* Begin XCBuildConfiguration section */ 495 | 9EDCE3F61C09D211002FA4A7 /* Debug */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | ALWAYS_SEARCH_USER_PATHS = NO; 499 | APPLICATION_EXTENSION_API_ONLY = YES; 500 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 501 | CLANG_CXX_LIBRARY = "libc++"; 502 | CLANG_ENABLE_MODULES = YES; 503 | CLANG_ENABLE_OBJC_ARC = YES; 504 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 505 | CLANG_WARN_BOOL_CONVERSION = YES; 506 | CLANG_WARN_COMMA = YES; 507 | CLANG_WARN_CONSTANT_CONVERSION = YES; 508 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 509 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 510 | CLANG_WARN_EMPTY_BODY = YES; 511 | CLANG_WARN_ENUM_CONVERSION = YES; 512 | CLANG_WARN_INFINITE_RECURSION = YES; 513 | CLANG_WARN_INT_CONVERSION = YES; 514 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 515 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 516 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 517 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 518 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 519 | CLANG_WARN_STRICT_PROTOTYPES = YES; 520 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 521 | CLANG_WARN_UNREACHABLE_CODE = YES; 522 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 523 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 524 | COPY_PHASE_STRIP = NO; 525 | CURRENT_PROJECT_VERSION = 1; 526 | DEBUG_INFORMATION_FORMAT = dwarf; 527 | ENABLE_STRICT_OBJC_MSGSEND = YES; 528 | ENABLE_TESTABILITY = YES; 529 | GCC_C_LANGUAGE_STANDARD = gnu99; 530 | GCC_DYNAMIC_NO_PIC = NO; 531 | GCC_NO_COMMON_BLOCKS = YES; 532 | GCC_OPTIMIZATION_LEVEL = 0; 533 | GCC_PREPROCESSOR_DEFINITIONS = ( 534 | "DEBUG=1", 535 | "$(inherited)", 536 | ); 537 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 538 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 539 | GCC_WARN_UNDECLARED_SELECTOR = YES; 540 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 541 | GCC_WARN_UNUSED_FUNCTION = YES; 542 | GCC_WARN_UNUSED_VARIABLE = YES; 543 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 544 | MTL_ENABLE_DEBUG_INFO = YES; 545 | ONLY_ACTIVE_ARCH = YES; 546 | SDKROOT = iphoneos; 547 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 548 | SWIFT_VERSION = 4.0; 549 | VALID_ARCHS = "arm64 armv7 armv7s x86_64"; 550 | VERSIONING_SYSTEM = "apple-generic"; 551 | VERSION_INFO_PREFIX = ""; 552 | }; 553 | name = Debug; 554 | }; 555 | 9EDCE3F71C09D211002FA4A7 /* Release */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | ALWAYS_SEARCH_USER_PATHS = NO; 559 | APPLICATION_EXTENSION_API_ONLY = YES; 560 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 561 | CLANG_CXX_LIBRARY = "libc++"; 562 | CLANG_ENABLE_MODULES = YES; 563 | CLANG_ENABLE_OBJC_ARC = YES; 564 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 565 | CLANG_WARN_BOOL_CONVERSION = YES; 566 | CLANG_WARN_COMMA = YES; 567 | CLANG_WARN_CONSTANT_CONVERSION = YES; 568 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 569 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 570 | CLANG_WARN_EMPTY_BODY = YES; 571 | CLANG_WARN_ENUM_CONVERSION = YES; 572 | CLANG_WARN_INFINITE_RECURSION = YES; 573 | CLANG_WARN_INT_CONVERSION = YES; 574 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 575 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 576 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 577 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 578 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 579 | CLANG_WARN_STRICT_PROTOTYPES = YES; 580 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 581 | CLANG_WARN_UNREACHABLE_CODE = YES; 582 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 583 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 584 | COPY_PHASE_STRIP = NO; 585 | CURRENT_PROJECT_VERSION = 1; 586 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 587 | ENABLE_NS_ASSERTIONS = NO; 588 | ENABLE_STRICT_OBJC_MSGSEND = YES; 589 | GCC_C_LANGUAGE_STANDARD = gnu99; 590 | GCC_NO_COMMON_BLOCKS = YES; 591 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 592 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 593 | GCC_WARN_UNDECLARED_SELECTOR = YES; 594 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 595 | GCC_WARN_UNUSED_FUNCTION = YES; 596 | GCC_WARN_UNUSED_VARIABLE = YES; 597 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 598 | MTL_ENABLE_DEBUG_INFO = NO; 599 | ONLY_ACTIVE_ARCH = NO; 600 | SDKROOT = iphoneos; 601 | SWIFT_VERSION = 4.0; 602 | VALIDATE_PRODUCT = YES; 603 | VALID_ARCHS = "arm64 armv7 armv7s x86_64"; 604 | VERSIONING_SYSTEM = "apple-generic"; 605 | VERSION_INFO_PREFIX = ""; 606 | }; 607 | name = Release; 608 | }; 609 | 9EDCE3F91C09D211002FA4A7 /* Debug */ = { 610 | isa = XCBuildConfiguration; 611 | buildSettings = { 612 | APPLICATION_EXTENSION_API_ONLY = YES; 613 | CLANG_ENABLE_MODULES = YES; 614 | CODE_SIGN_IDENTITY = ""; 615 | DEFINES_MODULE = YES; 616 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 617 | DYLIB_COMPATIBILITY_VERSION = 1; 618 | DYLIB_CURRENT_VERSION = 1; 619 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 620 | INFOPLIST_FILE = Info.plist; 621 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 622 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 623 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.Pantomime; 624 | PRODUCT_NAME = Pantomime; 625 | SKIP_INSTALL = YES; 626 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 627 | SWIFT_VERSION = 4.2; 628 | }; 629 | name = Debug; 630 | }; 631 | 9EDCE3FA1C09D211002FA4A7 /* Release */ = { 632 | isa = XCBuildConfiguration; 633 | buildSettings = { 634 | APPLICATION_EXTENSION_API_ONLY = YES; 635 | CLANG_ENABLE_MODULES = YES; 636 | CODE_SIGN_IDENTITY = ""; 637 | DEFINES_MODULE = YES; 638 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 639 | DYLIB_COMPATIBILITY_VERSION = 1; 640 | DYLIB_CURRENT_VERSION = 1; 641 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 642 | INFOPLIST_FILE = Info.plist; 643 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 644 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 645 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.Pantomime; 646 | PRODUCT_NAME = Pantomime; 647 | SKIP_INSTALL = YES; 648 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 649 | SWIFT_VERSION = 4.2; 650 | }; 651 | name = Release; 652 | }; 653 | 9EDCE3FC1C09D211002FA4A7 /* Debug */ = { 654 | isa = XCBuildConfiguration; 655 | buildSettings = { 656 | APPLICATION_EXTENSION_API_ONLY = NO; 657 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 658 | INFOPLIST_FILE = PantomimeTests/Info.plist; 659 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 660 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.PantomimeTests; 661 | PRODUCT_NAME = "$(TARGET_NAME)"; 662 | SWIFT_VERSION = 4.2; 663 | }; 664 | name = Debug; 665 | }; 666 | 9EDCE3FD1C09D211002FA4A7 /* Release */ = { 667 | isa = XCBuildConfiguration; 668 | buildSettings = { 669 | APPLICATION_EXTENSION_API_ONLY = NO; 670 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 671 | INFOPLIST_FILE = PantomimeTests/Info.plist; 672 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 673 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.PantomimeTests; 674 | PRODUCT_NAME = "$(TARGET_NAME)"; 675 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 676 | SWIFT_VERSION = 4.2; 677 | }; 678 | name = Release; 679 | }; 680 | ACE597931C18830D0031451F /* Debug */ = { 681 | isa = XCBuildConfiguration; 682 | buildSettings = { 683 | CLANG_ENABLE_MODULES = YES; 684 | CODE_SIGN_IDENTITY = "iPhone Developer"; 685 | DEFINES_MODULE = YES; 686 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 687 | DYLIB_COMPATIBILITY_VERSION = 1; 688 | DYLIB_CURRENT_VERSION = 1; 689 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 690 | ENABLE_BITCODE = YES; 691 | INFOPLIST_FILE = Info.plist; 692 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 693 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 694 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.Pantomime; 695 | PRODUCT_NAME = Pantomime; 696 | SDKROOT = appletvos; 697 | SKIP_INSTALL = YES; 698 | SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; 699 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 700 | SWIFT_VERSION = 4.0; 701 | VALID_ARCHS = "$(ARCHS_STANDARD)"; 702 | }; 703 | name = Debug; 704 | }; 705 | ACE597941C18830D0031451F /* Release */ = { 706 | isa = XCBuildConfiguration; 707 | buildSettings = { 708 | CLANG_ENABLE_MODULES = YES; 709 | CODE_SIGN_IDENTITY = ""; 710 | DEFINES_MODULE = YES; 711 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 712 | DYLIB_COMPATIBILITY_VERSION = 1; 713 | DYLIB_CURRENT_VERSION = 1; 714 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 715 | ENABLE_BITCODE = YES; 716 | INFOPLIST_FILE = Info.plist; 717 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 718 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 719 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.Pantomime; 720 | PRODUCT_NAME = Pantomime; 721 | SDKROOT = appletvos; 722 | SKIP_INSTALL = YES; 723 | SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; 724 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 725 | SWIFT_VERSION = 4.0; 726 | VALID_ARCHS = "$(ARCHS_STANDARD)"; 727 | }; 728 | name = Release; 729 | }; 730 | ACE597A41C18832E0031451F /* Debug */ = { 731 | isa = XCBuildConfiguration; 732 | buildSettings = { 733 | CLANG_ENABLE_MODULES = YES; 734 | CODE_SIGN_IDENTITY = "Mac Developer"; 735 | CODE_SIGN_STYLE = Automatic; 736 | COMBINE_HIDPI_IMAGES = YES; 737 | DEFINES_MODULE = YES; 738 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 739 | DYLIB_COMPATIBILITY_VERSION = 1; 740 | DYLIB_CURRENT_VERSION = 1; 741 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 742 | INFOPLIST_FILE = Info.plist; 743 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 744 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 745 | MACOSX_DEPLOYMENT_TARGET = 10.10; 746 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.Pantomime; 747 | PRODUCT_NAME = Pantomime; 748 | PROVISIONING_PROFILE_SPECIFIER = ""; 749 | SDKROOT = macosx; 750 | SKIP_INSTALL = YES; 751 | SUPPORTED_PLATFORMS = macosx; 752 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 753 | SWIFT_VERSION = 4.0; 754 | VALID_ARCHS = "i386 x86_64"; 755 | }; 756 | name = Debug; 757 | }; 758 | ACE597A51C18832E0031451F /* Release */ = { 759 | isa = XCBuildConfiguration; 760 | buildSettings = { 761 | CLANG_ENABLE_MODULES = YES; 762 | CODE_SIGN_IDENTITY = "Mac Developer"; 763 | CODE_SIGN_STYLE = Automatic; 764 | COMBINE_HIDPI_IMAGES = YES; 765 | DEFINES_MODULE = YES; 766 | DEVELOPMENT_TEAM = W9ZVWEMJZF; 767 | DYLIB_COMPATIBILITY_VERSION = 1; 768 | DYLIB_CURRENT_VERSION = 1; 769 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 770 | INFOPLIST_FILE = Info.plist; 771 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 772 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 773 | MACOSX_DEPLOYMENT_TARGET = 10.10; 774 | PRODUCT_BUNDLE_IDENTIFIER = com.nordija.Pantomime; 775 | PRODUCT_NAME = Pantomime; 776 | PROVISIONING_PROFILE_SPECIFIER = ""; 777 | SDKROOT = macosx; 778 | SKIP_INSTALL = YES; 779 | SUPPORTED_PLATFORMS = macosx; 780 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 781 | SWIFT_VERSION = 4.0; 782 | VALID_ARCHS = "i386 x86_64"; 783 | }; 784 | name = Release; 785 | }; 786 | /* End XCBuildConfiguration section */ 787 | 788 | /* Begin XCConfigurationList section */ 789 | 9EDCE3DE1C09D211002FA4A7 /* Build configuration list for PBXProject "Pantomime" */ = { 790 | isa = XCConfigurationList; 791 | buildConfigurations = ( 792 | 9EDCE3F61C09D211002FA4A7 /* Debug */, 793 | 9EDCE3F71C09D211002FA4A7 /* Release */, 794 | ); 795 | defaultConfigurationIsVisible = 0; 796 | defaultConfigurationName = Release; 797 | }; 798 | 9EDCE3F81C09D211002FA4A7 /* Build configuration list for PBXNativeTarget "Pantomime (iOS)" */ = { 799 | isa = XCConfigurationList; 800 | buildConfigurations = ( 801 | 9EDCE3F91C09D211002FA4A7 /* Debug */, 802 | 9EDCE3FA1C09D211002FA4A7 /* Release */, 803 | ); 804 | defaultConfigurationIsVisible = 0; 805 | defaultConfigurationName = Release; 806 | }; 807 | 9EDCE3FB1C09D211002FA4A7 /* Build configuration list for PBXNativeTarget "PantomimeTests" */ = { 808 | isa = XCConfigurationList; 809 | buildConfigurations = ( 810 | 9EDCE3FC1C09D211002FA4A7 /* Debug */, 811 | 9EDCE3FD1C09D211002FA4A7 /* Release */, 812 | ); 813 | defaultConfigurationIsVisible = 0; 814 | defaultConfigurationName = Release; 815 | }; 816 | ACE597921C18830D0031451F /* Build configuration list for PBXNativeTarget "Pantomime (tvOS)" */ = { 817 | isa = XCConfigurationList; 818 | buildConfigurations = ( 819 | ACE597931C18830D0031451F /* Debug */, 820 | ACE597941C18830D0031451F /* Release */, 821 | ); 822 | defaultConfigurationIsVisible = 0; 823 | defaultConfigurationName = Release; 824 | }; 825 | ACE597A31C18832E0031451F /* Build configuration list for PBXNativeTarget "Pantomime (OSX)" */ = { 826 | isa = XCConfigurationList; 827 | buildConfigurations = ( 828 | ACE597A41C18832E0031451F /* Debug */, 829 | ACE597A51C18832E0031451F /* Release */, 830 | ); 831 | defaultConfigurationIsVisible = 0; 832 | defaultConfigurationName = Release; 833 | }; 834 | /* End XCConfigurationList section */ 835 | }; 836 | rootObject = 9EDCE3DB1C09D211002FA4A7 /* Project object */; 837 | } 838 | -------------------------------------------------------------------------------- /Pantomime.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pantomime.xcodeproj/xcshareddata/xcschemes/Pantomime (OSX).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Pantomime.xcodeproj/xcshareddata/xcschemes/Pantomime (iOS).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Pantomime.xcodeproj/xcshareddata/xcschemes/Pantomime (tvOS).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /PantomimeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PantomimeTests/PantomimeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PantomimeTests.swift 3 | // PantomimeTests 4 | // 5 | // Created by Thomas Christensen on 24/08/16. 6 | // Copyright © 2016 Nordija A/S. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Pantomime 11 | 12 | class PantomimeTests: XCTestCase { 13 | 14 | func testParseMediaPlaylist() { 15 | let bundle = Bundle(for: type(of: self)) 16 | let path = bundle.path(forResource: "media", ofType: "m3u8")! 17 | 18 | let manifestBuilder = ManifestBuilder() 19 | let mediaPlaylist = manifestBuilder.parseMediaPlaylistFromFile(path, onMediaSegment: { 20 | (segment: MediaSegment) -> Void in 21 | XCTAssertNotNil(segment.sequence) 22 | }) 23 | 24 | XCTAssert(mediaPlaylist.targetDuration == 10) 25 | XCTAssert(mediaPlaylist.mediaSequence == 0) 26 | XCTAssert(mediaPlaylist.segments.count == 3) 27 | XCTAssert(mediaPlaylist.segments[0].title == " no desc") 28 | XCTAssert(mediaPlaylist.segments[0].subrangeLength == 100) 29 | XCTAssert(mediaPlaylist.segments[0].subrangeStart == 40) 30 | XCTAssert(mediaPlaylist.segments[1].subrangeLength == 100) 31 | XCTAssert(mediaPlaylist.segments[1].subrangeStart == nil) 32 | XCTAssert(mediaPlaylist.segments[2].duration == Float(3.003)) 33 | XCTAssert(mediaPlaylist.segments[2].path! == "http://media.example.com/third.ts") 34 | XCTAssert(mediaPlaylist.duration() == Float(21.021)) 35 | 36 | if let path2 = bundle.path(forResource: "media2", ofType: "m3u8") { 37 | let mediaPlaylist2 = manifestBuilder.parseMediaPlaylistFromFile(path2, onMediaSegment: { 38 | (segment: MediaSegment) -> Void in 39 | XCTAssertNotNil(segment.sequence) 40 | }) 41 | XCTAssertEqual(12, mediaPlaylist2.targetDuration, "Should have been read as 12 seconds") 42 | } 43 | } 44 | 45 | func testParseMasterPlaylist() { 46 | let bundle = Bundle(for: type(of: self)) 47 | let path = bundle.path(forResource: "master", ofType: "m3u8")! 48 | 49 | let manifestBuilder = ManifestBuilder() 50 | 51 | let masterPlaylist = manifestBuilder.parseMasterPlaylistFromFile(path, 52 | onMediaPlaylist: { 53 | (playlist: MediaPlaylist) -> Void in 54 | XCTAssertNotNil(playlist.programId) 55 | XCTAssertNotNil(playlist.bandwidth) 56 | XCTAssertNotNil(playlist.path) 57 | }) 58 | 59 | XCTAssert(masterPlaylist.playlists.count == 4) 60 | } 61 | 62 | /** 63 | * This would be the typical set up. Users will use Alomofire or similar libraries to 64 | * fetch the manifest files and then parse the text. 65 | */ 66 | func testParseFromString() { 67 | let bundle = Bundle(for: type(of: self)) 68 | let file = bundle.path(forResource: "master", ofType: "m3u8")! 69 | let path = URL(fileURLWithPath: file) 70 | do { 71 | 72 | let manifestText = try String(contentsOf: path, encoding: String.Encoding.utf8) 73 | let manifestBuilder = ManifestBuilder() 74 | let masterPlaylist = manifestBuilder.parseMasterPlaylistFromString(manifestText) 75 | XCTAssert(masterPlaylist.playlists.count == 4) 76 | 77 | } catch { 78 | XCTFail("Failed to read master playlist file") 79 | } 80 | } 81 | 82 | func testRealWorldParsing() { 83 | let manifestBuilder = ManifestBuilder() 84 | 85 | // Keep baseURL separate to contruct the nested media playlist URL's 86 | let baseURL = "http://devimages.apple.com/iphone/samples/bipbop" 87 | let path = "bipbopall.m3u8" 88 | let URL = Foundation.URL(string: baseURL + "/" + path)! 89 | XCTAssertEqual("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", URL.absoluteString) 90 | 91 | let expectation = self.expectation(description: "Testing parsing of the apple bipbop HTTP Live Stream sample") 92 | 93 | let session = URLSession.shared 94 | 95 | // Request master playlist 96 | let task = session.dataTask(with: URL, completionHandler: { 97 | data, response, error in 98 | 99 | XCTAssertNotNil(data, "data should not be nil") 100 | XCTAssertNil(error, "error should be nil") 101 | 102 | if let httpResponse = response as? HTTPURLResponse, 103 | let responseURL = httpResponse.url, 104 | let mimeType = httpResponse.mimeType { 105 | 106 | XCTAssertEqual(responseURL.absoluteString, URL.absoluteString, "No redirect expected") 107 | XCTAssertEqual(httpResponse.statusCode, 200, "HTTP response status code should be 200") 108 | XCTAssertEqual(mimeType, "audio/x-mpegurl", "HTTP response content type should be text/html") 109 | 110 | // Parse master playlist and perform verification of it 111 | if let dataFound = data, let manifestText = String(data: dataFound, encoding: String.Encoding.utf8) { 112 | 113 | let masterPlaylist = manifestBuilder.parseMasterPlaylistFromString(manifestText, 114 | onMediaPlaylist: { 115 | (mep: MediaPlaylist) -> Void in 116 | 117 | // Deduct full media playlist URL from path 118 | if let path = mep.path, let mepURL = Foundation.URL(string: baseURL + "/" + path) { 119 | 120 | // Request each found media playlist 121 | let mepTask = session.dataTask(with: mepURL, completionHandler: { 122 | mepData, mepResponse, mepError in 123 | 124 | XCTAssertNotNil(mepData, "data should not be nil") 125 | XCTAssertNil(mepError, "error should be nil") 126 | 127 | // Parse the media playlist and perform validation 128 | if let mepDataFound = mepData, 129 | let mepManifest = String(data: mepDataFound, encoding: String.Encoding.utf8) { 130 | let mediaPlaylist = manifestBuilder.parseMediaPlaylistFromString(mepManifest) 131 | XCTAssertEqual(181, mediaPlaylist.segments.count) 132 | } 133 | 134 | // In case we have requested, parsed and validated the last one 135 | if path.contains("gear4/prog_index.m3u8") { 136 | expectation.fulfill() 137 | } 138 | }) 139 | 140 | mepTask.resume() 141 | } 142 | }) 143 | 144 | XCTAssertEqual(4, masterPlaylist.playlists.count) 145 | } 146 | } else { 147 | XCTFail("Response was not NSHTTPURLResponse") 148 | } 149 | }) 150 | 151 | task.resume() 152 | 153 | waitForExpectations(timeout: task.originalRequest!.timeoutInterval) { 154 | error in 155 | if let error = error { 156 | XCTFail("Error: \(error.localizedDescription)") 157 | } 158 | task.cancel() 159 | } 160 | } 161 | 162 | func testParsingJustUsingStringSource() { 163 | let builder = ManifestBuilder() 164 | 165 | // Check using String with contentsOfURL 166 | do { 167 | if let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { 168 | let content = try String(contentsOf: url, encoding: String.Encoding.utf8) 169 | let master = builder.parseMasterPlaylistFromString(content) 170 | XCTAssertEqual(4, master.playlists.count, "Number of media playlists in master does not match") 171 | } else { 172 | XCTFail("Failed to create plain URL to bipbopall,m3u8") 173 | } 174 | } catch { 175 | XCTFail("Failed to just use string to read from various sources") 176 | } 177 | } 178 | 179 | func testSimpleFullParse() { 180 | let builder = ManifestBuilder() 181 | if let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { 182 | let manifest = builder.parse(url) 183 | XCTAssertEqual(4, manifest.playlists.count) 184 | } 185 | } 186 | 187 | func testFullParse() { 188 | let builder = ManifestBuilder() 189 | if let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { 190 | let manifest = builder.parse(url, onMediaPlaylist: { 191 | (media: MediaPlaylist) -> Void in 192 | XCTAssertNotNil(media.path) 193 | }, onMediaSegment: { 194 | (segment: MediaSegment) -> Void in 195 | let mediaManifestURL = url.URLByReplacingLastPathComponent(segment.mediaPlaylist!.path!) 196 | let segmentURL = mediaManifestURL!.URLByReplacingLastPathComponent(segment.path!) 197 | XCTAssertNotNil(segmentURL!.absoluteString) 198 | }) 199 | XCTAssertEqual(4, manifest.playlists.count, "Number of media playlists in master does not match") 200 | XCTAssertEqual(181, manifest.playlists[3].segments.count, "Segments not correctly parsed") 201 | } 202 | } 203 | 204 | func testFullParseWithFullPathInManifests() { 205 | let builder = ManifestBuilder() 206 | if let url = URL(string: "https://mnmedias.api.telequebec.tv/m3u8/29880.m3u8") { 207 | let manifest = builder.parse(url, onMediaPlaylist: { 208 | (media: MediaPlaylist) -> Void in 209 | XCTAssertNotNil(media.path) 210 | }, onMediaSegment: { 211 | (segment: MediaSegment) -> Void in 212 | let mediaManifestURL = url.URLByReplacingLastPathComponent(segment.mediaPlaylist!.path!) 213 | let segmentURL = mediaManifestURL!.URLByReplacingLastPathComponent(segment.path!) 214 | XCTAssertNotNil(segmentURL!.absoluteString) 215 | }) 216 | XCTAssertEqual(7, manifest.playlists.count, "Number of media playlists in master does not match") 217 | XCTAssertEqual(105, manifest.playlists[3].segments.count, "Segments not correctly parsed") 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /PantomimeTests/PlaylistTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 29/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import Pantomime 8 | 9 | class PlaylistTests: XCTestCase { 10 | 11 | func testMasterPlaylist() { 12 | let master = MasterPlaylist() 13 | XCTAssertNil(master.getPlaylist(0)) 14 | XCTAssertNil(master.getPlaylist(5)) 15 | let media = MediaPlaylist() 16 | master.addPlaylist(media) 17 | XCTAssertEqual(1, master.getPlaylistCount()) 18 | XCTAssertNotNil(master.getPlaylist(0)) 19 | XCTAssert(media === master.getPlaylist(0)) 20 | master.path = "hello" 21 | XCTAssertEqual("hello", master.path) 22 | } 23 | 24 | func testMediaPlaylist() { 25 | let media = MediaPlaylist() 26 | let segment = MediaSegment() 27 | XCTAssertNil(media.getSegment(0)) 28 | XCTAssertNil(media.getSegment(2)) 29 | media.addSegment(segment) 30 | XCTAssertEqual(1, media.getSegmentCount()) 31 | XCTAssertNotNil(media.getSegment(0)) 32 | XCTAssert(segment === media.getSegment(0)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PantomimeTests/ReaderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 26/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | @testable import Pantomime 8 | 9 | class ReaderTests: XCTestCase { 10 | func testReaderBuilder() { 11 | do { 12 | let stringReader = try ReaderBuilder.createReader(.stringreader, reference: "This is a line\nThis is another") 13 | XCTAssert(stringReader is StringBufferedReader) 14 | XCTAssertEqual("This is a line", stringReader.readLine()) 15 | XCTAssertEqual("This is another", stringReader.readLine()) 16 | XCTAssertNil(stringReader.readLine()) 17 | XCTAssertNil(stringReader.readLine()) 18 | 19 | let bundle = Bundle(for: type(of: self)) 20 | let path = bundle.path(forResource: "media", ofType: "m3u8")! 21 | let fileReader = try ReaderBuilder.createReader(.filereader, reference: path) 22 | XCTAssert(fileReader is FileBufferedReader) 23 | XCTAssertEqual("#EXTM3U", fileReader.readLine()) 24 | XCTAssertEqual("#This is a comment", fileReader.readLine()) 25 | for _ in 1...10 { 26 | _ = fileReader.readLine()! 27 | } 28 | XCTAssertNil(fileReader.readLine()) 29 | 30 | let httpReader = try ReaderBuilder.createReader(.httpreader, reference: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") 31 | XCTAssert(httpReader is URLBufferedReader) 32 | XCTAssertEqual("#EXTM3U", httpReader.readLine()) 33 | XCTAssertEqual("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000", httpReader.readLine()) 34 | XCTAssertEqual("gear1/prog_index.m3u8", httpReader.readLine()) 35 | XCTAssertEqual("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=311111", httpReader.readLine()) 36 | XCTAssertEqual("gear2/prog_index.m3u8", httpReader.readLine()) 37 | XCTAssertEqual("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=484444", httpReader.readLine()) 38 | XCTAssertEqual("gear3/prog_index.m3u8", httpReader.readLine()) 39 | XCTAssertEqual("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=737777", httpReader.readLine()) 40 | XCTAssertEqual("gear4/prog_index.m3u8", httpReader.readLine()) 41 | } catch { 42 | XCTFail("Not able to construct valid buffered reader instances") 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PantomimeTests/master.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000 3 | gear1/prog_index.m3u8 4 | #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=311111 5 | gear2/prog_index.m3u8 6 | #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=484444 7 | gear3/prog_index.m3u8 8 | #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=737777 9 | gear4/prog_index.m3u8 10 | 11 | -------------------------------------------------------------------------------- /PantomimeTests/media.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #This is a comment 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXTINF:9.009, no desc 6 | #EXT-X-BYTERANGE:100@40 7 | http://media.example.com/first.ts 8 | #EXTINF:9.009, 9 | #EXT-X-BYTERANGE:100 10 | http://media.example.com/second.ts 11 | #EXTINF:3.003, 12 | http://media.example.com/third.ts -------------------------------------------------------------------------------- /PantomimeTests/media2.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:3 3 | #EXT-X-ALLOW-CACHE:NO 4 | #EXT-X-TARGETDURATION:12 5 | #EXT-X-MEDIA-SEQUENCE:29945 6 | #EXTINF:10.04, 7 | media_w346121679_b864000_sleng_29945.ts 8 | #EXTINF:9.92, 9 | media_w346121679_b864000_sleng_29946.ts 10 | #EXTINF:10.08, 11 | media_w346121679_b864000_sleng_29947.ts 12 | #EXTINF:10.08, 13 | media_w346121679_b864000_sleng_29948.ts 14 | #EXTINF:10.08, 15 | media_w346121679_b864000_sleng_29949.ts -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pantomime 2 | 3 | Pantomime is a lightweight framework for iOS, OSX and tvOS that can read and parse HTTP Live Streaming manifests. 4 | 5 | ## Latest build 6 | 7 | [![Build Status](https://travis-ci.org/thomaschristensen/Pantomime.svg?branch=master)](https://travis-ci.org/thomaschristensen/Pantomime) 8 | 9 | ## Installation 10 | 11 | You can use [Carthage](https://github.com/Carthage/Carthage) to install Pantomime by adding that to your Cartfile: 12 | 13 | ``` 14 | github "thomaschristensen/Pantomime" 15 | ``` 16 | 17 | #### via CocoaPods 18 | 19 | To use [CocoaPods](https://cocoapods.org) just add this to your Podfile: 20 | 21 | ``` Ruby 22 | pod 'Pantomime' 23 | ``` 24 | 25 | #### via Swift Package Manager 26 | 27 | To use Pantomime as a [Swift Package Manager](https://swift.org/package-manager/) package just add the following in your Package.swift file. 28 | 29 | ``` Swift 30 | import PackageDescription 31 | 32 | let package = Package( 33 | name: "HelloWorld", 34 | dependencies: [ 35 | .Package(url: "https://github.com/thomaschristensen/Pantomime", majorVersion: 0) 36 | ] 37 | ) 38 | ``` 39 | 40 |
41 | 42 | ## Usage 43 | 44 | To use the parser just do the following: 45 | 46 | ``` Swift 47 | import Pantomime 48 | 49 | let builder = ManifestBuilder() 50 | if let url = NSURL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8") { 51 | let manifest = builder.parse(url) 52 | } 53 | ``` 54 | 55 | The ManifestBuilder's parse method expects a master playlist manifest 56 | to be found at the other end of the URL. Once this has been parsed 57 | all Media Playlist Manifests referred to in the master will also 58 | be fetched and parsed. 59 | 60 | ## Core Classes 61 | 62 | #### ManifestBuilder 63 | The manifest builder can parse both Master and Media playlist manifests. 64 | You can choose to let ManifestBuilder parse master and it's media 65 | playlists, or you can parse either the master or media playlists only. 66 | 67 | #### MasterPlaylist 68 | Represents a master playlist and it holds a reference to a list of 69 | media playlist objects 70 | 71 | #### MediaPlaylist 72 | The media playlist object contains a list of all video segments and 73 | other properties like target duration (max duration of each segment), 74 | path, version, bandwidth, program-id and the starting media sequence 75 | number. 76 | 77 | #### MediaSegment 78 | This object holds a reference to the actual video file (path), it's 79 | actual duration, sequence number and optional title. 80 | 81 | ## Helper Classes 82 | 83 | #### BufferedReader 84 | This is a protocol that defines how these text manifest documents can 85 | be read line by line. Various implementations of this protocol exist 86 | helping reading the documents from a File, a URL or from a String. 87 | These implementations are FileBufferedReader, URLBufferedReader and 88 | StringBufferedReader. 89 | 90 | #### ReaderBuilder 91 | Is a utility class that can be used to construct the actual 92 | implementation of BufferedReader by specifying which type is required. 93 | (This class is already deprecated. Use the implementations of 94 | BufferedReader directly) 95 | 96 | #### NSURLExtension 97 | An extension to the NSURL class has been made to assist in constructing 98 | the right URL when given relative paths in the various manifest files. 99 | 100 | ``` Swift 101 | 102 | let masterManifest = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8" 103 | let mediaManifest = "gear1/prog_index.m3u8" 104 | 105 | if let masterManifestURL = NSURL(string: masterManifest) { 106 | let mediaManifestURL = masterManifestURL.URLByReplacingLastPathComponent(mediaManifest) 107 | // mediaManifestURL now = http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8 108 | } 109 | 110 | ``` 111 | 112 | 113 | ## License 114 | 115 | The Pantomime Framework is released under the [MIT License](https://github.com/thomaschristensen/Pantomime/blob/master/LICENSE). 116 | 117 | ## Todo 118 | 119 | * Construct Master and Media Playlist objects and write them as files -------------------------------------------------------------------------------- /sources/BufferedReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 26/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | public protocol BufferedReader { 9 | func close() 10 | 11 | // Return next line or nil if no more lines can be read 12 | func readLine() -> String? 13 | } 14 | -------------------------------------------------------------------------------- /sources/FileBufferedReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 26/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | open class FileBufferedReader: BufferedReader { 9 | var _fileName: String 10 | var streamReader: StreamReader 11 | 12 | public init(path: String) { 13 | _fileName = path 14 | streamReader = StreamReader(path: _fileName)! 15 | } 16 | 17 | open func close() { 18 | streamReader.close() 19 | } 20 | 21 | open func readLine() -> String? { 22 | return streamReader.nextLine() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sources/ManifestBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 25/08/16. 3 | // Copyright (c) 2016 Nordija A/S. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /** 9 | * Parses HTTP Live Streaming manifest files 10 | * Use a BufferedReader to let the parser read from various sources. 11 | */ 12 | open class ManifestBuilder { 13 | 14 | public init() {} 15 | 16 | /** 17 | * Parses Master playlist manifests 18 | */ 19 | fileprivate func parseMasterPlaylist(_ reader: BufferedReader, onMediaPlaylist: 20 | ((_ playlist: MediaPlaylist) -> Void)?) -> MasterPlaylist { 21 | var masterPlaylist = MasterPlaylist() 22 | var currentMediaPlaylist: MediaPlaylist? 23 | 24 | defer { 25 | reader.close() 26 | } 27 | while let line = reader.readLine() { 28 | if line.isEmpty { 29 | // Skip empty lines 30 | 31 | } else if line.hasPrefix("#EXT") { 32 | 33 | // Tags 34 | if line.hasPrefix("#EXTM3U") { 35 | // Ok Do nothing 36 | 37 | } else if line.hasPrefix("#EXT-X-STREAM-INF") { 38 | // #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000 39 | currentMediaPlaylist = MediaPlaylist() 40 | do { 41 | let programIdString = try line.replace("(.*)=(\\d+),(.*)", replacement: "$2") 42 | let bandwidthString = try line.replace("(.*),(.*)=(\\d+)(.*)", replacement: "$3") 43 | if let currentMediaPlaylistExist = currentMediaPlaylist { 44 | currentMediaPlaylistExist.programId = Int(programIdString)! 45 | currentMediaPlaylistExist.bandwidth = Int(bandwidthString)! 46 | } 47 | } catch { 48 | print("Failed to parse program-id and bandwidth on master playlist. Line = \(line)") 49 | } 50 | 51 | } 52 | } else if line.hasPrefix("#") { 53 | // Comments are ignored 54 | 55 | } else { 56 | // URI - must be 57 | if let currentMediaPlaylistExist = currentMediaPlaylist { 58 | currentMediaPlaylistExist.path = line 59 | currentMediaPlaylistExist.masterPlaylist = masterPlaylist 60 | masterPlaylist.addPlaylist(currentMediaPlaylistExist) 61 | if let callableOnMediaPlaylist = onMediaPlaylist { 62 | callableOnMediaPlaylist(currentMediaPlaylistExist) 63 | } 64 | } 65 | } 66 | } 67 | 68 | return masterPlaylist 69 | } 70 | 71 | /** 72 | * Parses Media Playlist manifests 73 | */ 74 | fileprivate func parseMediaPlaylist(_ reader: BufferedReader, 75 | mediaPlaylist: MediaPlaylist = MediaPlaylist(), 76 | onMediaSegment: ((_ segment: MediaSegment) -> Void)?) -> MediaPlaylist { 77 | var currentSegment: MediaSegment? 78 | var currentURI: String? 79 | var currentSequence = 0 80 | 81 | defer { 82 | reader.close() 83 | } 84 | 85 | while let line = reader.readLine() { 86 | if line.isEmpty { 87 | // Skip empty lines 88 | 89 | } else if line.hasPrefix("#EXT") { 90 | 91 | // Tags 92 | if line.hasPrefix("#EXTM3U") { 93 | 94 | // Ok Do nothing 95 | } else if line.hasPrefix("#EXT-X-VERSION") { 96 | do { 97 | let version = try line.replace("(.*):(\\d+)(.*)", replacement: "$2") 98 | mediaPlaylist.version = Int(version) 99 | } catch { 100 | print("Failed to parse the version of media playlist. Line = \(line)") 101 | } 102 | 103 | } else if line.hasPrefix("#EXT-X-TARGETDURATION") { 104 | do { 105 | let durationString = try line.replace("(.*):(\\d+)(.*)", replacement: "$2") 106 | mediaPlaylist.targetDuration = Int(durationString) 107 | } catch { 108 | print("Failed to parse the target duration of media playlist. Line = \(line)") 109 | } 110 | 111 | } else if line.hasPrefix("#EXT-X-MEDIA-SEQUENCE") { 112 | do { 113 | let mediaSequence = try line.replace("(.*):(\\d+)(.*)", replacement: "$2") 114 | if let mediaSequenceExtracted = Int(mediaSequence) { 115 | mediaPlaylist.mediaSequence = mediaSequenceExtracted 116 | currentSequence = mediaSequenceExtracted 117 | } 118 | } catch { 119 | print("Failed to parse the media sequence in media playlist. Line = \(line)") 120 | } 121 | 122 | } else if line.hasPrefix("#EXTINF") { 123 | currentSegment = MediaSegment() 124 | do { 125 | let segmentDurationString = try line.replace("(.*):(\\d.*),(.*)", replacement: "$2") 126 | let segmentTitle = try line.replace("(.*):(\\d.*),(.*)", replacement: "$3") 127 | currentSegment!.duration = Float(segmentDurationString) 128 | currentSegment!.title = segmentTitle 129 | } catch { 130 | print("Failed to parse the segment duration and title. Line = \(line)") 131 | } 132 | } else if line.hasPrefix("#EXT-X-BYTERANGE") { 133 | if line.contains("@") { 134 | do { 135 | let subrangeLength = try line.replace("(.*):(\\d.*)@(.*)", replacement: "$2") 136 | let subrangeStart = try line.replace("(.*):(\\d.*)@(.*)", replacement: "$3") 137 | currentSegment!.subrangeLength = Int(subrangeLength) 138 | currentSegment!.subrangeStart = Int(subrangeStart) 139 | } catch { 140 | print("Failed to parse byte range. Line = \(line)") 141 | } 142 | } else { 143 | do { 144 | let subrangeLength = try line.replace("(.*):(\\d.*)", replacement: "$2") 145 | currentSegment!.subrangeLength = Int(subrangeLength) 146 | currentSegment!.subrangeStart = nil 147 | } catch { 148 | print("Failed to parse the byte range. Line = \(line)") 149 | } 150 | } 151 | } else if line.hasPrefix("#EXT-X-DISCONTINUITY") { 152 | currentSegment!.discontinuity = true 153 | } 154 | 155 | } else if line.hasPrefix("#") { 156 | // Comments are ignored 157 | 158 | } else { 159 | // URI - must be 160 | if let currentSegmentExists = currentSegment { 161 | currentSegmentExists.mediaPlaylist = mediaPlaylist 162 | currentSegmentExists.path = line 163 | currentSegmentExists.sequence = currentSequence 164 | currentSequence += 1 165 | mediaPlaylist.addSegment(currentSegmentExists) 166 | if let callableOnMediaSegment = onMediaSegment { 167 | callableOnMediaSegment(currentSegmentExists) 168 | } 169 | } 170 | } 171 | } 172 | 173 | return mediaPlaylist 174 | } 175 | 176 | /** 177 | * Parses the master playlist manifest from a string document. 178 | * 179 | * Convenience method that uses a StringBufferedReader as source for the manifest. 180 | */ 181 | open func parseMasterPlaylistFromString(_ string: String, onMediaPlaylist: 182 | ((_ playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { 183 | return parseMasterPlaylist(StringBufferedReader(string: string), onMediaPlaylist: onMediaPlaylist) 184 | } 185 | 186 | /** 187 | * Parses the master playlist manifest from a file. 188 | * 189 | * Convenience method that uses a FileBufferedReader as source for the manifest. 190 | */ 191 | open func parseMasterPlaylistFromFile(_ path: String, onMediaPlaylist: 192 | ((_ playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { 193 | return parseMasterPlaylist(FileBufferedReader(path: path), onMediaPlaylist: onMediaPlaylist) 194 | } 195 | 196 | /** 197 | * Parses the master playlist manifest requested synchronous from a URL 198 | * 199 | * Convenience method that uses a URLBufferedReader as source for the manifest. 200 | */ 201 | open func parseMasterPlaylistFromURL(_ url: URL, onMediaPlaylist: 202 | ((_ playlist: MediaPlaylist) -> Void)? = nil) -> MasterPlaylist { 203 | return parseMasterPlaylist(URLBufferedReader(uri: url), onMediaPlaylist: onMediaPlaylist) 204 | } 205 | 206 | /** 207 | * Parses the media playlist manifest from a string document. 208 | * 209 | * Convenience method that uses a StringBufferedReader as source for the manifest. 210 | */ 211 | open func parseMediaPlaylistFromString(_ string: String, 212 | mediaPlaylist: MediaPlaylist = MediaPlaylist(), 213 | onMediaSegment:((_ segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { 214 | return parseMediaPlaylist(StringBufferedReader(string: string), 215 | mediaPlaylist: mediaPlaylist, onMediaSegment: onMediaSegment) 216 | } 217 | 218 | /** 219 | * Parses the media playlist manifest from a file document. 220 | * 221 | * Convenience method that uses a FileBufferedReader as source for the manifest. 222 | */ 223 | open func parseMediaPlaylistFromFile(_ path: String, 224 | mediaPlaylist: MediaPlaylist = MediaPlaylist(), 225 | onMediaSegment: ((_ segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { 226 | return parseMediaPlaylist(FileBufferedReader(path: path), 227 | mediaPlaylist: mediaPlaylist, onMediaSegment: onMediaSegment) 228 | } 229 | 230 | /** 231 | * Parses the media playlist manifest requested synchronous from a URL 232 | * 233 | * Convenience method that uses a URLBufferedReader as source for the manifest. 234 | */ 235 | @discardableResult 236 | open func parseMediaPlaylistFromURL(_ url: URL, 237 | mediaPlaylist: MediaPlaylist = MediaPlaylist(), 238 | onMediaSegment: ((_ segment: MediaSegment) -> Void)? = nil) -> MediaPlaylist { 239 | return parseMediaPlaylist(URLBufferedReader(uri: url), 240 | mediaPlaylist: mediaPlaylist, onMediaSegment: onMediaSegment) 241 | } 242 | 243 | /** 244 | * Parses the master manifest found at the URL and all the referenced media playlist manifests recursively. 245 | */ 246 | open func parse(_ url: URL, 247 | onMediaPlaylist: ((_ playlist: MediaPlaylist) -> Void)? = nil, 248 | onMediaSegment: ((_ segment: MediaSegment) -> Void)? = nil) -> MasterPlaylist { 249 | // Parse master 250 | let master = parseMasterPlaylistFromURL(url, onMediaPlaylist: onMediaPlaylist) 251 | for playlist in master.playlists { 252 | if let path = playlist.path { 253 | 254 | // Detect if manifests are referred to with protocol 255 | if path.hasPrefix("http") || path.hasPrefix("file") { 256 | // Full path used 257 | if let mediaURL = URL(string: path) { 258 | parseMediaPlaylistFromURL(mediaURL, 259 | mediaPlaylist: playlist, onMediaSegment: onMediaSegment) 260 | } 261 | } else { 262 | // Relative path used 263 | if let mediaURL = url.URLByReplacingLastPathComponent(path) { 264 | parseMediaPlaylistFromURL(mediaURL, 265 | mediaPlaylist: playlist, onMediaSegment: onMediaSegment) 266 | } 267 | } 268 | } 269 | } 270 | return master 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /sources/MasterPlaylist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterPlaylist.swift 3 | // Pantomime 4 | // 5 | // Created by Thomas Christensen on 25/08/16. 6 | // Copyright © 2016 Sebastian Kreutzberger. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class MasterPlaylist { 12 | var playlists = [MediaPlaylist]() 13 | open var path: String? 14 | 15 | public init() {} 16 | 17 | open func addPlaylist(_ playlist: MediaPlaylist) { 18 | playlists.append(playlist) 19 | } 20 | 21 | open func getPlaylist(_ index: Int) -> MediaPlaylist? { 22 | if index >= playlists.count { 23 | return nil 24 | } 25 | return playlists[index] 26 | } 27 | 28 | open func getPlaylistCount() -> Int { 29 | return playlists.count 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sources/MediaPlaylist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 24/08/16. 3 | // Copyright (c) 2016 Nordija A/S. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | open class MediaPlaylist { 9 | var masterPlaylist: MasterPlaylist? 10 | 11 | open var programId: Int = 0 12 | open var bandwidth: Int = 0 13 | open var path: String? 14 | open var version: Int? 15 | open var targetDuration: Int? 16 | open var mediaSequence: Int? 17 | var segments = [MediaSegment]() 18 | 19 | public init() { 20 | 21 | } 22 | 23 | open func addSegment(_ segment: MediaSegment) { 24 | segments.append(segment) 25 | } 26 | 27 | open func getSegment(_ index: Int) -> MediaSegment? { 28 | if index >= segments.count { 29 | return nil 30 | } 31 | return segments[index] 32 | } 33 | 34 | open func getSegmentCount() -> Int { 35 | return segments.count 36 | } 37 | 38 | open func duration() -> Float { 39 | var dur: Float = 0.0 40 | for item in segments { 41 | dur += item.duration! 42 | } 43 | return dur 44 | } 45 | 46 | open func getMaster() -> MasterPlaylist? { 47 | return self.masterPlaylist 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sources/MediaSegment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 24/08/16. 3 | // Copyright (c) 2016 Nordija A/S. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | open class MediaSegment { 9 | var mediaPlaylist: MediaPlaylist? 10 | open var duration: Float? 11 | open var sequence: Int = 0 12 | open var subrangeLength: Int? 13 | open var subrangeStart: Int? 14 | open var title: String? 15 | open var discontinuity: Bool = false 16 | open var path: String? 17 | 18 | public init() { 19 | 20 | } 21 | 22 | open func getMediaPlaylist() -> MediaPlaylist? { 23 | return self.mediaPlaylist 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sources/NSURLExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 27/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // Extend the NSURL object with helpers 9 | 10 | public extension URL { 11 | /** 12 | Replaces the last path component of the URL with the path component and returns a new URL or nil. 13 | 14 | - Parameter pathComponent: path component to replace last path component with 15 | 16 | - Returns: A new URL object or nil 17 | */ 18 | @available(iOS 4.0, *) 19 | public func URLByReplacingLastPathComponent(_ pathComponent: String) -> URL? { 20 | let tmpurl = self.deletingLastPathComponent() 21 | return tmpurl.appendingPathComponent(pathComponent) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sources/ReaderBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 26/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | open class ReaderBuilder { 9 | 10 | enum ReaderBuilderError: Error { 11 | case illegalReference(reference:String) 12 | } 13 | 14 | enum ReaderTypes { 15 | case stringreader, httpreader, filereader 16 | } 17 | 18 | static func createURLReader(_ reference: URL) -> BufferedReader { 19 | return URLBufferedReader(uri: reference) 20 | } 21 | 22 | static func createStringReader(_ reference: String) -> BufferedReader { 23 | return StringBufferedReader(string: reference) 24 | } 25 | 26 | static func createFileReader(_ reference: String) -> BufferedReader? { 27 | return FileBufferedReader(path: reference) 28 | } 29 | 30 | static func createReader(_ reader: ReaderTypes, reference: String) throws -> BufferedReader { 31 | 32 | switch reader { 33 | case .stringreader: 34 | return StringBufferedReader(string: reference) 35 | case .filereader: 36 | return FileBufferedReader(path: reference) 37 | case .httpreader: 38 | if let uriOK = URL(string: reference) { 39 | return URLBufferedReader(uri: uriOK) 40 | } else { 41 | throw ReaderBuilderError.illegalReference(reference: reference) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sources/StreamReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 24/08/16. 3 | // Copyright (c) 2016 Nordija A/S. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | class StreamReader { 9 | 10 | let encoding: String.Encoding 11 | let chunkSize: Int 12 | 13 | var fileHandle: FileHandle! 14 | let buffer: NSMutableData! 15 | let delimData: Data! 16 | var atEof: Bool = false 17 | 18 | init?(path: String, delimiter: String = "\n", encoding: String.Encoding = String.Encoding.utf8, 19 | chunkSize: Int = 4096) { 20 | self.chunkSize = chunkSize 21 | self.encoding = encoding 22 | 23 | if let fileHandle = FileHandle(forReadingAtPath: path), 24 | let delimData = delimiter.data(using: encoding), 25 | let buffer = NSMutableData(capacity: chunkSize) { 26 | self.fileHandle = fileHandle 27 | self.delimData = delimData 28 | self.buffer = buffer 29 | } else { 30 | self.fileHandle = nil 31 | self.delimData = nil 32 | self.buffer = nil 33 | return nil 34 | } 35 | } 36 | 37 | deinit { 38 | self.close() 39 | } 40 | 41 | /// Return next line, or nil on EOF. 42 | func nextLine() -> String? { 43 | precondition(fileHandle != nil, "Attempt to read from closed file") 44 | 45 | if atEof { 46 | return nil 47 | } 48 | 49 | // Read data chunks from file until a line delimiter is found: 50 | 51 | var range = buffer.range(of: delimData, options: [], in: NSRange(location: 0, length: buffer.length)) 52 | while range.location == NSNotFound { 53 | let tmpData = fileHandle.readData(ofLength: chunkSize) 54 | if tmpData.isEmpty { 55 | // EOF or read error. 56 | atEof = true 57 | if buffer.length > 0 { 58 | // Buffer contains last line in file (not terminated by delimiter). 59 | let line = NSString(data: buffer as Data, encoding: encoding.rawValue) 60 | 61 | buffer.length = 0 62 | return line as String? 63 | } 64 | // No more lines. 65 | return nil 66 | } 67 | buffer.append(tmpData) 68 | range = buffer.range(of: delimData, options: [], in: NSRange(location: 0, length: buffer.length)) 69 | } 70 | 71 | // Convert complete line (excluding the delimiter) to a string: 72 | let line = NSString(data: buffer.subdata(with: NSRange(location: 0, length: range.location)), 73 | encoding: encoding.rawValue) 74 | // Remove line (and the delimiter) from the buffer: 75 | buffer.replaceBytes( in: NSRange(location: 0, length: range.location + range.length), 76 | withBytes: nil, length: 0) 77 | 78 | return line as String? 79 | } 80 | 81 | /// Start reading from the beginning of file. 82 | func rewind() { 83 | fileHandle.seek(toFileOffset: 0) 84 | buffer.length = 0 85 | atEof = false 86 | } 87 | 88 | /// Close the underlying file. No reading must be done after calling this method. 89 | func close() { 90 | fileHandle?.closeFile() 91 | fileHandle = nil 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /sources/StringBufferedReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 26/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /** 9 | * Uses a string as a stream and reads it line by line. 10 | */ 11 | 12 | open class StringBufferedReader: BufferedReader { 13 | var _buffer: [String] 14 | var _line: Int 15 | 16 | public init(string: String) { 17 | _line = 0 18 | _buffer = string.components(separatedBy: CharacterSet.newlines) 19 | } 20 | 21 | open func close() { 22 | } 23 | 24 | open func readLine() -> String? { 25 | if _buffer.isEmpty || _buffer.count <= _line { 26 | return nil 27 | } 28 | let result = _buffer[_line] 29 | _line += 1 30 | return result 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sources/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 24/08/16. 3 | // Copyright (c) 2016 Nordija A/S. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | // Extend the String object with helpers 9 | extension String { 10 | 11 | // String.replace(); similar to JavaScript's String.replace() and Ruby's String.gsub() 12 | func replace(_ pattern: String, replacement: String) throws -> String { 13 | 14 | let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) 15 | 16 | return regex.stringByReplacingMatches( 17 | in: self, 18 | options: [.withTransparentBounds], 19 | range: NSRange(location: 0, length: self.count), 20 | withTemplate: replacement 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sources/URLBufferedReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Thomas Christensen on 26/08/16. 3 | // Copyright (c) 2016 Sebastian Kreutzberger. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /** 9 | * Reads the document found at the specified URL in one chunk synchonous 10 | * and then lets the readLine function pick it line by line. 11 | */ 12 | open class URLBufferedReader: BufferedReader { 13 | var _uri: URL 14 | var _stringReader: StringBufferedReader 15 | 16 | public init(uri: URL) { 17 | _uri = uri 18 | _stringReader = StringBufferedReader(string: "") 19 | let request1: URLRequest = URLRequest(url: _uri) 20 | let response: AutoreleasingUnsafeMutablePointer? = nil 21 | do { 22 | let dataVal = try NSURLConnection.sendSynchronousRequest(request1, returning: response) 23 | let text = String(data: dataVal, encoding: String.Encoding.utf8)! 24 | _stringReader = StringBufferedReader(string: text) 25 | } catch { 26 | print("Failed to make request for content at \(_uri)") 27 | } 28 | } 29 | 30 | open func close() { 31 | _stringReader.close() 32 | } 33 | 34 | open func readLine() -> String? { 35 | return _stringReader.readLine() 36 | } 37 | 38 | } 39 | --------------------------------------------------------------------------------