Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b93581515 | |||
| 31b9e89dba |
+24
@@ -7,10 +7,12 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
27E3EC64A90305ACA68AE35A7DC597E0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */; };
|
||||
2A421C2A94DF56A00FF73322C6B470C8 /* SwiftAudioPlayer-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E268C8D5FBBF7E0E790D3AA6A70FEC2 /* SwiftAudioPlayer-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3A31FEF49CC8C3B757EEB4EBCC9BCCF4 /* Pods-SwiftAudioPlayer_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 351771425C270B04BF2A07F0262DA192 /* Pods-SwiftAudioPlayer_Tests-dummy.m */; };
|
||||
418D41690EF20077112E2BE86E32FB6A /* Pods-SwiftAudioPlayer_Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AB41D88A2C694FBDF26EA56381EED25F /* Pods-SwiftAudioPlayer_Example-dummy.m */; };
|
||||
79D8DF73FA7CDD6E266BAE71D46E035F /* Pods-SwiftAudioPlayer_Tests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 50C71346CE708A211A5AFAC20BAE48CB /* Pods-SwiftAudioPlayer_Tests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
831B263D357A5FA2DDC7B1AE4B374092 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */; };
|
||||
8F93DB166237195ED222EE55B6404625 /* Pods-SwiftAudioPlayer_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B0B76CB1439F4D361322144E5A65C3A /* Pods-SwiftAudioPlayer_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
A40DBE292391D9CA00F86146 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40DBE282391D9C900F86146 /* Data.swift */; };
|
||||
A411CE4625F9609D0039E1CD /* SAPlayerFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = A411CE4525F9609D0039E1CD /* SAPlayerFeatures.swift */; };
|
||||
@@ -51,6 +53,7 @@
|
||||
A4FBA6B5221B74C900D5A353 /* SALockScreenInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B3221B74C900D5A353 /* SALockScreenInfo.swift */; };
|
||||
A4FBA6B7221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */; };
|
||||
A4FBA6B9221BAF8700D5A353 /* SAAudioAvailabilityRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B8221BAF8700D5A353 /* SAAudioAvailabilityRange.swift */; };
|
||||
B73D01578ABBDB6FF402D868A6C547FF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */; };
|
||||
E08AD6157EF688FE832F866CBCDA3532 /* SwiftAudioPlayer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FB83B3B4253D41C37C5563D34D450BF8 /* SwiftAudioPlayer-dummy.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -87,6 +90,7 @@
|
||||
509D93CD81F074F6E7C4B9DE13210ACF /* Pods_SwiftAudioPlayer_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAudioPlayer_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
50C71346CE708A211A5AFAC20BAE48CB /* Pods-SwiftAudioPlayer_Tests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SwiftAudioPlayer_Tests-umbrella.h"; sourceTree = "<group>"; };
|
||||
55AB0CDF00C23619C7F54FE21D0C9534 /* Pods-SwiftAudioPlayer_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftAudioPlayer_Example-frameworks.sh"; sourceTree = "<group>"; };
|
||||
5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
69AF5444212FEC2674325627F26305AD /* Pods-SwiftAudioPlayer_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftAudioPlayer_Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
6EC04ECC8F7CB2AF2E4E042A6A8ECFA1 /* SwiftAudioPlayer.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; path = SwiftAudioPlayer.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
70839C5AD428953FAF3091E814FF6E31 /* Pods-SwiftAudioPlayer_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-SwiftAudioPlayer_Example.modulemap"; sourceTree = "<group>"; };
|
||||
@@ -152,6 +156,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B73D01578ABBDB6FF402D868A6C547FF /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -159,6 +164,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
27E3EC64A90305ACA68AE35A7DC597E0 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -166,6 +172,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
831B263D357A5FA2DDC7B1AE4B374092 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -211,6 +218,14 @@
|
||||
path = "Target Support Files/Pods-SwiftAudioPlayer_Tests";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5E0D919E635D23B70123790B8308F8EF /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */,
|
||||
);
|
||||
name = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5F444B7A1C462A30A1CA4CCD3A7CF7B0 /* Targets Support Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -243,6 +258,7 @@
|
||||
children = (
|
||||
93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */,
|
||||
D2A5FF8756A6E3EEEA69006E1A3C81F7 /* Development Pods */,
|
||||
BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */,
|
||||
21D946895A4F57F51246F3EBCF330719 /* Products */,
|
||||
5F444B7A1C462A30A1CA4CCD3A7CF7B0 /* Targets Support Files */,
|
||||
);
|
||||
@@ -369,6 +385,14 @@
|
||||
path = Directors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5E0D919E635D23B70123790B8308F8EF /* iOS */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D2A5FF8756A6E3EEEA69006E1A3C81F7 /* Development Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
41B4A1BE666DAEDD342DBACF /* Pods_SwiftAudioPlayer_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E9F82E3AA46F1DA40F32F7F /* Pods_SwiftAudioPlayer_Tests.framework */; };
|
||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
|
||||
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
|
||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
|
||||
@@ -14,6 +15,7 @@
|
||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
|
||||
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; };
|
||||
A470FEE2260303DA00F135FF /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = A470FEE1260303DA00F135FF /* Model.swift */; };
|
||||
E5808EC0557FB2395AA56468 /* Pods_SwiftAudioPlayer_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E5C0E3F3235B6FFE85EF425 /* Pods_SwiftAudioPlayer_Example.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -28,7 +30,9 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0B7D1E6C00E83B4AF8AA1781 /* Pods-SwiftAudioPlayer_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAudioPlayer_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftAudioPlayer_Tests/Pods-SwiftAudioPlayer_Tests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
1E5C0E3F3235B6FFE85EF425 /* Pods_SwiftAudioPlayer_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAudioPlayer_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4B5DD2AE0B23A759D18926DC /* Pods-SwiftAudioPlayer_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAudioPlayer_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftAudioPlayer_Example/Pods-SwiftAudioPlayer_Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
4E9F82E3AA46F1DA40F32F7F /* Pods_SwiftAudioPlayer_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAudioPlayer_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACD01AFB9204008FA782 /* SwiftAudioPlayer_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAudioPlayer_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -52,6 +56,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E5808EC0557FB2395AA56468 /* Pods_SwiftAudioPlayer_Example.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -59,12 +64,22 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
41B4A1BE666DAEDD342DBACF /* Pods_SwiftAudioPlayer_Tests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
408E805A4561B2F63083E539 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1E5C0E3F3235B6FFE85EF425 /* Pods_SwiftAudioPlayer_Example.framework */,
|
||||
4E9F82E3AA46F1DA40F32F7F /* Pods_SwiftAudioPlayer_Tests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4246ED1215E81CA7B8F0AB36 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -84,6 +99,7 @@
|
||||
607FACE81AFB9204008FA782 /* Tests */,
|
||||
607FACD11AFB9204008FA782 /* Products */,
|
||||
4246ED1215E81CA7B8F0AB36 /* Pods */,
|
||||
408E805A4561B2F63083E539 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -116,32 +116,30 @@
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Skip Silences" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="M2y-FP-H1D">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Skip Silences" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="M2y-FP-H1D">
|
||||
<rect key="frame" x="89" y="504" width="101" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="2cn-E5-TeQ">
|
||||
<rect key="frame" x="226" y="499" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="2cn-E5-TeQ">
|
||||
<rect key="frame" x="226" y="499" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="skipSilencesSwitched:" destination="vXZ-lx-hvc" eventType="valueChanged" id="p7X-Y8-7hO"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="IGe-aU-Y6D">
|
||||
<rect key="frame" x="226" y="540" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="IGe-aU-Y6D">
|
||||
<rect key="frame" x="226" y="540" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="sleepSwitched:" destination="vXZ-lx-hvc" eventType="valueChanged" id="noa-m8-VHy"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sleep After 5 s" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vf6-kr-yWa">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Sleep After 5 s" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vf6-kr-yWa">
|
||||
<rect key="frame" x="83" y="545" width="112" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Loop" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JOr-pf-CKN">
|
||||
<rect key="frame" x="152" y="588" width="38" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -160,22 +158,14 @@
|
||||
<action selector="streamTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="AXY-N7-87Y"/>
|
||||
</connections>
|
||||
</button>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="cfU-Rp-Kqf">
|
||||
<rect key="frame" x="226" y="583" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="loopSwitched:" destination="vXZ-lx-hvc" eventType="valueChanged" id="psj-Vs-9BI"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="nsl-df-P21" firstAttribute="top" secondItem="y5i-MZ-Qat" secondAttribute="bottom" constant="8" id="0aM-Sz-J9k"/>
|
||||
<constraint firstItem="lTK-Hd-Tl2" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="16" id="1wb-IW-jYz"/>
|
||||
<constraint firstItem="j3w-gr-HzF" firstAttribute="leading" secondItem="lTK-Hd-Tl2" secondAttribute="leading" id="26c-ZJ-768"/>
|
||||
<constraint firstItem="JOr-pf-CKN" firstAttribute="top" secondItem="vf6-kr-yWa" secondAttribute="bottom" constant="22" id="4UI-XL-M9D"/>
|
||||
<constraint firstItem="jUc-tP-CC5" firstAttribute="top" secondItem="KDu-ea-kF8" secondAttribute="bottom" constant="80" id="5sT-An-9vw"/>
|
||||
<constraint firstItem="6d9-Bc-hIz" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="KDu-ea-kF8" secondAttribute="trailing" constant="8" symbolic="YES" id="60t-zV-EiY"/>
|
||||
<constraint firstItem="2cn-E5-TeQ" firstAttribute="centerY" secondItem="M2y-FP-H1D" secondAttribute="centerY" id="6QX-Ru-ZbO"/>
|
||||
<constraint firstItem="joK-xi-MCo" firstAttribute="leading" secondItem="lTK-Hd-Tl2" secondAttribute="leading" id="7KA-Mg-HFD"/>
|
||||
<constraint firstItem="vfk-OJ-S3T" firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" id="8PP-Pp-1Hc"/>
|
||||
<constraint firstItem="joK-xi-MCo" firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" id="AH1-Uu-eLB"/>
|
||||
@@ -184,27 +174,19 @@
|
||||
<constraint firstItem="Urj-Dv-41y" firstAttribute="centerY" secondItem="j3w-gr-HzF" secondAttribute="centerY" id="Fvd-7V-Rr8"/>
|
||||
<constraint firstItem="1IX-z5-wWx" firstAttribute="leading" secondItem="joK-xi-MCo" secondAttribute="leading" id="GeX-7f-jzu"/>
|
||||
<constraint firstItem="0QE-3F-a4G" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="jUc-tP-CC5" secondAttribute="trailing" constant="8" symbolic="YES" id="JP5-yW-eVB"/>
|
||||
<constraint firstItem="cfU-Rp-Kqf" firstAttribute="leading" secondItem="JOr-pf-CKN" secondAttribute="trailing" constant="36" id="JxU-kl-pkL"/>
|
||||
<constraint firstItem="yUQ-mI-ozK" firstAttribute="top" secondItem="w2a-RA-zmI" secondAttribute="bottom" constant="100" id="K1K-8N-SpD"/>
|
||||
<constraint firstItem="IGe-aU-Y6D" firstAttribute="centerY" secondItem="vf6-kr-yWa" secondAttribute="centerY" id="K1s-td-R7b"/>
|
||||
<constraint firstItem="vf6-kr-yWa" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="83" id="M0b-b2-UnQ"/>
|
||||
<constraint firstItem="vfk-OJ-S3T" firstAttribute="leading" secondItem="lTK-Hd-Tl2" secondAttribute="leading" id="NOY-IO-NIJ"/>
|
||||
<constraint firstItem="tFH-sY-Xu9" firstAttribute="centerY" secondItem="jUc-tP-CC5" secondAttribute="centerY" id="Rre-EY-kVY"/>
|
||||
<constraint firstItem="KDu-ea-kF8" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="43" id="SRU-sX-z5b"/>
|
||||
<constraint firstItem="cfU-Rp-Kqf" firstAttribute="centerY" secondItem="JOr-pf-CKN" secondAttribute="centerY" id="Tox-y4-XVg"/>
|
||||
<constraint firstItem="w2a-RA-zmI" firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" id="Vki-IZ-AdN"/>
|
||||
<constraint firstItem="lTK-Hd-Tl2" firstAttribute="top" secondItem="j3w-gr-HzF" secondAttribute="bottom" constant="8" id="Wwx-Uo-yIC"/>
|
||||
<constraint firstItem="IGe-aU-Y6D" firstAttribute="leading" secondItem="vf6-kr-yWa" secondAttribute="trailing" constant="31" id="XpW-wP-Iyh"/>
|
||||
<constraint firstItem="vf6-kr-yWa" firstAttribute="top" secondItem="M2y-FP-H1D" secondAttribute="bottom" constant="20" id="Y8L-El-ycq"/>
|
||||
<constraint firstItem="nsl-df-P21" firstAttribute="leading" secondItem="vfk-OJ-S3T" secondAttribute="leading" id="a5C-nZ-8Jc"/>
|
||||
<constraint firstItem="yUQ-mI-ozK" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="a66-h4-WVf"/>
|
||||
<constraint firstItem="Urj-Dv-41y" firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" id="aKt-EV-Bwd"/>
|
||||
<constraint firstItem="tFH-sY-Xu9" firstAttribute="top" secondItem="1IX-z5-wWx" secondAttribute="bottom" constant="27" id="bIq-V0-Sac"/>
|
||||
<constraint firstItem="M2y-FP-H1D" firstAttribute="top" secondItem="vfk-OJ-S3T" secondAttribute="bottom" constant="26" id="bsl-hj-xUt"/>
|
||||
<constraint firstItem="tFH-sY-Xu9" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="62.5" id="cH6-q6-Lel"/>
|
||||
<constraint firstItem="yUQ-mI-ozK" firstAttribute="top" secondItem="nsl-df-P21" secondAttribute="bottom" constant="8" id="cKV-wk-6P9"/>
|
||||
<constraint firstItem="jUc-tP-CC5" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="cgM-Nj-yit"/>
|
||||
<constraint firstItem="JOr-pf-CKN" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="152" id="cgd-E2-XpJ"/>
|
||||
<constraint firstItem="KDu-ea-kF8" firstAttribute="top" secondItem="joK-xi-MCo" secondAttribute="bottom" constant="32" id="dLw-rF-Pfb"/>
|
||||
<constraint firstItem="w2a-RA-zmI" firstAttribute="leading" secondItem="lTK-Hd-Tl2" secondAttribute="leading" id="daz-b0-eCC"/>
|
||||
<constraint firstItem="jUc-tP-CC5" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="tFH-sY-Xu9" secondAttribute="trailing" constant="8" symbolic="YES" id="fS9-Ce-4ph"/>
|
||||
@@ -212,13 +194,11 @@
|
||||
<constraint firstAttribute="trailing" secondItem="lTK-Hd-Tl2" secondAttribute="trailing" constant="16" id="gdg-7Y-7la"/>
|
||||
<constraint firstAttribute="trailing" secondItem="1IX-z5-wWx" secondAttribute="trailing" constant="16" id="hHM-jO-RZd"/>
|
||||
<constraint firstItem="pVf-cJ-9ca" firstAttribute="centerX" secondItem="joK-xi-MCo" secondAttribute="centerX" id="lOM-Fa-KdR"/>
|
||||
<constraint firstItem="2cn-E5-TeQ" firstAttribute="leading" secondItem="M2y-FP-H1D" secondAttribute="trailing" constant="36" id="laG-3h-LI7"/>
|
||||
<constraint firstItem="6d9-Bc-hIz" firstAttribute="top" secondItem="joK-xi-MCo" secondAttribute="bottom" constant="32" id="m9s-An-IWV"/>
|
||||
<constraint firstItem="vfk-OJ-S3T" firstAttribute="top" secondItem="yUQ-mI-ozK" secondAttribute="bottom" constant="8" id="oaW-rr-UVN"/>
|
||||
<constraint firstItem="nsl-df-P21" firstAttribute="trailing" secondItem="vfk-OJ-S3T" secondAttribute="trailing" id="r5e-Wq-dqV"/>
|
||||
<constraint firstItem="y5i-MZ-Qat" firstAttribute="centerX" secondItem="nsl-df-P21" secondAttribute="centerX" id="reC-GA-ZgT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0QE-3F-a4G" secondAttribute="trailing" constant="62.5" id="tg1-gr-hdd"/>
|
||||
<constraint firstItem="M2y-FP-H1D" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="89" id="vcF-gP-oe0"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6d9-Bc-hIz" secondAttribute="trailing" constant="44" id="vtN-y4-iqp"/>
|
||||
<constraint firstItem="0QE-3F-a4G" firstAttribute="centerY" secondItem="jUc-tP-CC5" secondAttribute="centerY" id="xDi-tj-bBF"/>
|
||||
<constraint firstItem="lTK-Hd-Tl2" firstAttribute="top" secondItem="jUc-tP-CC5" secondAttribute="bottom" constant="40" id="ytQ-s4-kJm"/>
|
||||
@@ -232,7 +212,6 @@
|
||||
<outlet property="currentUrlLocationLabel" destination="1IX-z5-wWx" id="MuO-fF-ZxL"/>
|
||||
<outlet property="downloadButton" destination="KDu-ea-kF8" id="5o4-1h-y06"/>
|
||||
<outlet property="durationLabel" destination="Urj-Dv-41y" id="mIq-eh-int"/>
|
||||
<outlet property="loopSwitch" destination="cfU-Rp-Kqf" id="wTZ-Sr-mV4"/>
|
||||
<outlet property="playPauseButton" destination="jUc-tP-CC5" id="e9C-zV-A1B"/>
|
||||
<outlet property="rateLabel" destination="yUQ-mI-ozK" id="Dx4-lO-A1B"/>
|
||||
<outlet property="rateSlider" destination="vfk-OJ-S3T" id="mNc-ET-aNM"/>
|
||||
|
||||
@@ -47,7 +47,7 @@ struct AudioInfo: Hashable {
|
||||
|
||||
var lockscreenInfo: SALockScreenInfo {
|
||||
get {
|
||||
return SALockScreenInfo(title: self.title, artist: self.artist, albumTitle: nil, artwork: nil, releaseDate: self.releaseDate)
|
||||
return SALockScreenInfo(title: self.title, artist: self.artist, artwork: nil, releaseDate: self.releaseDate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class ViewController: UIViewController {
|
||||
var isDownloading: Bool = false
|
||||
var isStreaming: Bool = false
|
||||
var beingSeeked: Bool = false
|
||||
var loopEnabled = false
|
||||
|
||||
|
||||
var downloadId: UInt?
|
||||
@@ -70,7 +69,6 @@ class ViewController: UIViewController {
|
||||
super.viewDidLoad()
|
||||
|
||||
SAPlayer.Downloader.allowUsingCellularData = true
|
||||
SAPlayer.shared.HTTPHeaderFields = ["User-Agent": "foobar"]
|
||||
|
||||
// SAPlayer.shared.DEBUG_MODE = true
|
||||
|
||||
@@ -134,7 +132,7 @@ class ViewController: UIViewController {
|
||||
// unsubscribeFromChanges()
|
||||
// subscribeToChanges()
|
||||
|
||||
SAPlayer.shared.mediaInfo = SALockScreenInfo(title: selectedAudio.title, artist: selectedAudio.artist, albumTitle: nil, artwork: UIImage(), releaseDate: selectedAudio.releaseDate)
|
||||
SAPlayer.shared.mediaInfo = SALockScreenInfo(title: selectedAudio.title, artist: selectedAudio.artist, artwork: UIImage(), releaseDate: selectedAudio.releaseDate)
|
||||
}
|
||||
|
||||
func checkIfAudioDownloaded() {
|
||||
@@ -214,10 +212,8 @@ class ViewController: UIViewController {
|
||||
self.playPauseButton.setTitle("Loading", for: .normal)
|
||||
return
|
||||
case .ended:
|
||||
if !self.loopEnabled {
|
||||
self.isPlayable = false
|
||||
self.playPauseButton.setTitle("Done", for: .normal)
|
||||
}
|
||||
self.isPlayable = false
|
||||
self.playPauseButton.setTitle("Done", for: .normal)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -263,12 +259,7 @@ class ViewController: UIViewController {
|
||||
@IBAction func rateChanged(_ sender: Any) {
|
||||
let speed = rateSlider.value
|
||||
rateLabel.text = "rate: \(speed)x"
|
||||
|
||||
if skipSilencesSwitch.isOn {
|
||||
SAPlayer.Features.SkipSilences.setRateSafely(speed) // if using Skip Silences, we need use this version of setting rate to safely change the rate with the feature enabled.
|
||||
} else {
|
||||
SAPlayer.shared.rate = speed
|
||||
}
|
||||
SAPlayer.shared.rate = speed
|
||||
}
|
||||
@IBAction func reverbChanged(_ sender: Any) {
|
||||
let reverb = reverbSlider.value
|
||||
@@ -339,6 +330,16 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
@IBAction func playPauseTouched(_ sender: Any) {
|
||||
// if lastPlayedAudioIndex != selectedAudio.index {
|
||||
// if let savedUrl = selectedAudio.savedUrl {
|
||||
// SAPlayer.shared.startSavedAudio(withSavedUrl: savedUrl)
|
||||
// } else {
|
||||
// SAPlayer.shared.startRemoteAudio(withRemoteUrl: selectedAudio.url)
|
||||
// }
|
||||
//
|
||||
// return
|
||||
// }
|
||||
|
||||
SAPlayer.shared.togglePlayAndPause()
|
||||
}
|
||||
|
||||
@@ -381,18 +382,5 @@ class ViewController: UIViewController {
|
||||
_ = SAPlayer.Features.SleepTimer.disable()
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var loopSwitch: UISwitch!
|
||||
|
||||
@IBAction func loopSwitched(_ sender: Any) {
|
||||
loopEnabled = loopSwitch.isOn
|
||||
|
||||
if loopSwitch.isOn {
|
||||
SAPlayer.Features.Loop.enable()
|
||||
} else {
|
||||
SAPlayer.Features.Loop.disable()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// swift-tools-version:5.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "SwiftAudioPlayer",
|
||||
platforms: [
|
||||
.iOS(.v10), .tvOS(.v10)
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "SwiftAudioPlayer",
|
||||
targets: ["SwiftAudioPlayer"])
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "SwiftAudioPlayer",
|
||||
path: "Source"
|
||||
)
|
||||
],
|
||||
swiftLanguageVersions: [.v5]
|
||||
)
|
||||
@@ -14,7 +14,6 @@ Thus, using [AudioToolbox](https://developer.apple.com/documentation/audiotoolbo
|
||||
|
||||
1. Realtime audio manipulation that includes going up to 10x speed, using [equalizers and other manipulations](https://developer.apple.com/documentation/avfaudio/avaudiouniteq)
|
||||
1. Stream online audio using AVAudioEngine
|
||||
1. Stream radio
|
||||
1. Play locally saved audio with the same API
|
||||
1. Download audio
|
||||
1. Queue up downloaded and streamed audio for autoplay
|
||||
@@ -117,9 +116,16 @@ For more details and specifics look at the [API documentation](#api-in-detail) b
|
||||
|
||||
## Contact
|
||||
|
||||
### Issues or questions
|
||||
### Issues
|
||||
|
||||
Submit any issues, requests, and questions [on the Github repo](https://github.com/tanhakabir/SwiftAudioPlayer/issues).
|
||||
Submit any issues or requests [on the Github repo](https://github.com/tanhakabir/SwiftAudioPlayer/issues).
|
||||
|
||||
### Any questions?
|
||||
|
||||
Feel free to reach out to either of us:
|
||||
|
||||
[tanhakabir](https://github.com/tanhakabir), tanhakabir.ca@gmail.com
|
||||
[JonMercer](https://github.com/JonMercer), mercer.jon@gmail.com
|
||||
|
||||
### License
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ class AudioDiskEngine: AudioEngine {
|
||||
|
||||
doRepeatedly(timeInterval: 0.2) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard self.playingStatus != .ended else { return }
|
||||
|
||||
self.updateIsPlaying()
|
||||
self.updateNeedle()
|
||||
|
||||
@@ -45,7 +45,6 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
var engine: AVAudioEngine!
|
||||
var playerNode: AVAudioPlayerNode!
|
||||
private var engineInvalidated: Bool = false
|
||||
|
||||
static let defaultEngineAudioFormat: AVAudioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 2, interleaved: false)!
|
||||
|
||||
@@ -103,8 +102,6 @@ class AudioEngine: AudioEngineProtocol {
|
||||
AudioClockDirector.shared.changeInAudioBuffered(key, buffered: bufferedSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
private var audioModifiers: [AVAudioUnit]?
|
||||
|
||||
init(url: AudioURL, delegate:AudioEngineDelegate?, engineAudioFormat: AVAudioFormat) {
|
||||
self.key = url.key
|
||||
@@ -118,40 +115,39 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
func initHelper(_ engineAudioFormat: AVAudioFormat) {
|
||||
engine.attach(playerNode)
|
||||
audioModifiers = SAPlayer.shared.audioModifiers
|
||||
|
||||
defer { engine.prepare() }
|
||||
|
||||
guard let audioModifiers = audioModifiers, audioModifiers.count > 0 else {
|
||||
engine.connect(playerNode, to: engine.mainMixerNode, format: engineAudioFormat)
|
||||
return
|
||||
|
||||
for node in SAPlayer.shared.audioModifiers {
|
||||
engine.attach(node)
|
||||
}
|
||||
|
||||
audioModifiers.forEach { engine.attach($0) }
|
||||
|
||||
var i = 0
|
||||
|
||||
let node = audioModifiers[i]
|
||||
engine.connect(playerNode, to: node, format: engineAudioFormat)
|
||||
|
||||
i += 1
|
||||
|
||||
while i < audioModifiers.count {
|
||||
let lastNode = audioModifiers[i - 1]
|
||||
let currNode = audioModifiers[i]
|
||||
|
||||
engine.connect(lastNode, to: currNode, format: engineAudioFormat)
|
||||
if SAPlayer.shared.audioModifiers.count > 0 {
|
||||
var i = 0
|
||||
|
||||
let node = SAPlayer.shared.audioModifiers[i]
|
||||
engine.connect(playerNode, to: node, format: engineAudioFormat)
|
||||
|
||||
i += 1
|
||||
|
||||
while i < SAPlayer.shared.audioModifiers.count {
|
||||
let lastNode = SAPlayer.shared.audioModifiers[i - 1]
|
||||
let currNode = SAPlayer.shared.audioModifiers[i]
|
||||
|
||||
engine.connect(lastNode, to: currNode, format: engineAudioFormat)
|
||||
i += 1
|
||||
}
|
||||
|
||||
let finalNode = SAPlayer.shared.audioModifiers[SAPlayer.shared.audioModifiers.count - 1]
|
||||
|
||||
engine.connect(finalNode, to: engine.mainMixerNode, format: engineAudioFormat)
|
||||
} else {
|
||||
engine.connect(playerNode, to: engine.mainMixerNode, format: engineAudioFormat)
|
||||
}
|
||||
|
||||
let finalNode = audioModifiers[audioModifiers.count - 1]
|
||||
|
||||
engine.connect(finalNode, to: engine.mainMixerNode, format: engineAudioFormat)
|
||||
|
||||
engine.prepare()
|
||||
}
|
||||
|
||||
deinit {
|
||||
if state == .resumed {
|
||||
playerNode.stop()
|
||||
engine.stop()
|
||||
}
|
||||
|
||||
@@ -176,7 +172,7 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { [weak self] (timer: Timer) in
|
||||
guard let self = self else { return }
|
||||
guard !self.engineInvalidated else {
|
||||
guard self.playingStatus != .ended else {
|
||||
self.delegate = nil
|
||||
return
|
||||
}
|
||||
@@ -197,6 +193,8 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
let isPlaying = engine.isRunning && playerNode.isPlaying
|
||||
playingStatus = isPlaying ? .playing : .paused
|
||||
|
||||
// playingStatus = .paused
|
||||
}
|
||||
|
||||
func play() {
|
||||
@@ -232,12 +230,6 @@ class AudioEngine: AudioEngineProtocol {
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
engineInvalidated = true
|
||||
playerNode.stop()
|
||||
engine.stop()
|
||||
|
||||
if let audioModifiers = audioModifiers, audioModifiers.count > 0 {
|
||||
audioModifiers.forEach { engine.detach($0) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,18 +74,12 @@ class AudioStreamEngine: AudioEngine {
|
||||
didSet {
|
||||
Log.debug("number of buffers scheduled in total: \(numberOfBuffersScheduledInTotal)")
|
||||
if numberOfBuffersScheduledInTotal == 0 {
|
||||
if playingStatus == .playing { wasPlaying = true }
|
||||
pause()
|
||||
// delegate?.didError()
|
||||
// TODO: we should not have an error here. We should instead have the throttler
|
||||
// propegate when it doesn't enough buffers while they were playing
|
||||
// TODO: "Make this a legitimate warning to user about needing more data from stream"
|
||||
}
|
||||
|
||||
if numberOfBuffersScheduledInTotal > MIN_BUFFERS_TO_BE_PLAYABLE && wasPlaying {
|
||||
wasPlaying = false
|
||||
play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +164,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
|
||||
doRepeatedly(timeInterval: timeInterval) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard self.playingStatus != .ended else { return }
|
||||
|
||||
self.repeatedUpdates()
|
||||
}
|
||||
@@ -208,7 +203,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
|
||||
Log.debug("processed buffer for engine of frame length \(nextScheduledBuffer.frameLength)")
|
||||
queue.async { [weak self] in
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
if #available(iOS 11.0, *) {
|
||||
// to make sure the pcm buffers are properly free'd from memory we need to nil them after the player has used them
|
||||
self?.playerNode.scheduleBuffer(nextScheduledBuffer, completionCallbackType: .dataConsumed, completionHandler: { (_) in
|
||||
nextScheduledBuffer = nil
|
||||
@@ -333,13 +328,7 @@ class AudioStreamEngine: AudioEngine {
|
||||
}
|
||||
|
||||
override func invalidate() {
|
||||
queue.sync { [weak self] in
|
||||
self?.invalidateHelperDispatchQueue()
|
||||
self?.converter.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private func invalidateHelperDispatchQueue() {
|
||||
super.invalidate()
|
||||
converter.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,6 @@ class AudioThrottler: AudioThrottleable {
|
||||
extension Array where Element == Data {
|
||||
var sum: Int {
|
||||
get {
|
||||
guard count > 0 else { return 0 }
|
||||
return self.reduce(0) { $0 + $1.count }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public struct SAAudioAvailabilityRange {
|
||||
var needleAtEnd = false
|
||||
|
||||
if(totalDurationBuffered > 0 && needle > 0) {
|
||||
needleAtEnd = needle >= totalDurationBuffered - 5
|
||||
needleAtEnd = needle >= totalDurationBuffered - 1
|
||||
}
|
||||
|
||||
// if most of the audio is buffered for long audio or in short audio there isn't many seconds left to buffer it means wwe've reached the end of the audio
|
||||
|
||||
@@ -39,7 +39,7 @@ extension LockScreenViewProtocol {
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = [:]
|
||||
}
|
||||
|
||||
@available(iOS 10.0, tvOS 10.0, *)
|
||||
@available(iOS 10.0, *)
|
||||
func setLockScreenInfo(withMediaInfo info: SALockScreenInfo?, duration: Duration) {
|
||||
var nowPlayingInfo:[String : Any] = [:]
|
||||
|
||||
@@ -50,7 +50,6 @@ extension LockScreenViewProtocol {
|
||||
|
||||
let title = info.title
|
||||
let artist = info.artist
|
||||
let albumTitle = info.albumTitle ?? artist
|
||||
let releaseDate = info.releaseDate
|
||||
|
||||
// For some reason we need to set a duration here for the needle?
|
||||
@@ -58,7 +57,7 @@ extension LockScreenViewProtocol {
|
||||
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = title
|
||||
nowPlayingInfo[MPMediaItemPropertyArtist] = artist
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = albumTitle
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = artist
|
||||
//nowPlayingInfo[MPMediaItemPropertyGenre] = //maybe later when we have it
|
||||
//nowPlayingInfo[MPMediaItemPropertyIsExplicit] = //maybe later when we have it
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumArtist] = artist
|
||||
@@ -169,10 +168,7 @@ extension LockScreenViewProtocol {
|
||||
}
|
||||
|
||||
func updateLockscreenSkipIntervals() {
|
||||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
commandCenter.skipBackwardCommand.isEnabled = skipBackwardSeconds > 0
|
||||
commandCenter.skipBackwardCommand.preferredIntervals = [skipBackwardSeconds] as [NSNumber]
|
||||
commandCenter.skipForwardCommand.isEnabled = skipForwardSeconds > 0
|
||||
commandCenter.skipForwardCommand.preferredIntervals = [skipForwardSeconds] as [NSNumber]
|
||||
MPRemoteCommandCenter.shared().skipBackwardCommand.preferredIntervals = [skipBackwardSeconds] as [NSNumber]
|
||||
MPRemoteCommandCenter.shared().skipForwardCommand.preferredIntervals = [skipForwardSeconds] as [NSNumber]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ protocol AudioDataManagable {
|
||||
|
||||
var allowCellular: Bool { get set }
|
||||
|
||||
func setHTTPHeaderFields(_ fields: [String: String]?)
|
||||
func setBackgroundCompletionHandler(_ completionHandler: @escaping () -> ())
|
||||
func setAllowCellularDownloadPreference(_ preference: Bool)
|
||||
|
||||
@@ -97,11 +96,6 @@ class AudioDataManager: AudioDataManagable {
|
||||
streamingCallbacks = []
|
||||
}
|
||||
|
||||
func setHTTPHeaderFields(_ fields: [String: String]?) {
|
||||
streamWorker.HTTPHeaderFields = fields
|
||||
downloadWorker.HTTPHeaderFields = fields
|
||||
}
|
||||
|
||||
func setBackgroundCompletionHandler(_ completionHandler: @escaping () -> ()) {
|
||||
backgroundCompletion = completionHandler
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@ protocol AudioDataDownloadable: AnyObject {
|
||||
var numberOfActive: Int { get }
|
||||
var numberOfQueued: Int { get }
|
||||
|
||||
var HTTPHeaderFields: [String: String]? { get set }
|
||||
|
||||
func getProgressOfDownload(withID id: ID) -> Double?
|
||||
|
||||
func start(withID id: ID, withRemoteUrl remoteUrl: URL, completion: @escaping (URL) -> ())
|
||||
@@ -59,8 +57,6 @@ class AudioDownloadWorker: NSObject, AudioDataDownloadable {
|
||||
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
|
||||
}()
|
||||
|
||||
var HTTPHeaderFields: [String: String]?
|
||||
|
||||
private var activeDownloads: [ActiveDownload] = []
|
||||
private var queuedDownloads = Set<DownloadInfo>()
|
||||
|
||||
@@ -115,10 +111,7 @@ class AudioDownloadWorker: NSObject, AudioDataDownloadable {
|
||||
|
||||
queuedDownloads.remove(info)
|
||||
|
||||
var request = URLRequest(url: info.remoteUrl)
|
||||
HTTPHeaderFields?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
|
||||
|
||||
let task: URLSessionDownloadTask = session.downloadTask(with: request)
|
||||
let task: URLSessionDownloadTask = session.downloadTask(with: info.remoteUrl)
|
||||
task.taskDescription = info.id
|
||||
|
||||
let activeTask = ActiveDownload(info: info, task: task)
|
||||
|
||||
@@ -44,9 +44,6 @@ import Foundation
|
||||
protocol AudioDataStreamable {
|
||||
//if user taps download then starts to stream
|
||||
init(progressCallback: @escaping (_ id: ID, _ dto: StreamProgressDTO) -> (), doneCallback: @escaping (_ id: ID, _ error: Error?)->Bool) //Bool is should save or not
|
||||
|
||||
var HTTPHeaderFields: [String: String]? { get set }
|
||||
|
||||
func start(withID id: ID, withRemoteURL url: URL, withInitialData data: Data?, andTotalBytesExpectedPreviously previousTotalBytesExpected: Int64?)
|
||||
func pause(withId id: ID)
|
||||
func resume(withId id: ID)
|
||||
@@ -69,8 +66,6 @@ class AudioStreamWorker:NSObject, AudioDataStreamable {
|
||||
fileprivate let doneCallback: (_ id: ID, _ error: Error?) -> Bool
|
||||
private var session: URLSession!
|
||||
|
||||
var HTTPHeaderFields: [String: String]?
|
||||
|
||||
private var id: ID?
|
||||
private var url: URL?
|
||||
private var task: URLSessionDataTask?
|
||||
@@ -94,7 +89,7 @@ class AudioStreamWorker:NSObject, AudioDataStreamable {
|
||||
|
||||
let config = URLSessionConfiguration.background(withIdentifier: "SwiftAudioPlayer.stream")
|
||||
// Specifies that the phone should keep trying till it receives connection instead of dropping immediately
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
if #available(iOS 11.0, *) {
|
||||
config.waitsForConnectivity = true
|
||||
}
|
||||
self.session = URLSession(configuration: config, delegate: self, delegateQueue: nil) //TODO: should we use ephemeral
|
||||
@@ -110,7 +105,6 @@ class AudioStreamWorker:NSObject, AudioDataStreamable {
|
||||
|
||||
if let data = data {
|
||||
var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: TIMEOUT)
|
||||
HTTPHeaderFields?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
|
||||
request.addValue("bytes=\(data.count)-", forHTTPHeaderField: "Range")
|
||||
task = session.dataTask(with: request)
|
||||
task?.taskDescription = id
|
||||
@@ -127,11 +121,10 @@ class AudioStreamWorker:NSObject, AudioDataStreamable {
|
||||
|
||||
task?.resume()
|
||||
} else {
|
||||
var request = URLRequest(url: url)
|
||||
HTTPHeaderFields?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
|
||||
task = session.dataTask(with: request)
|
||||
task?.taskDescription = id
|
||||
task = session.dataTask(with: url)
|
||||
task?.resume()
|
||||
|
||||
task?.taskDescription = id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +217,6 @@ class AudioStreamWorker:NSObject, AudioDataStreamable {
|
||||
self.progressCallback(id, StreamProgressDTO(progress: 0, data: Data(), totalBytesExpected: totalBytesExpectedForWholeFile))
|
||||
|
||||
var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: TIMEOUT)
|
||||
HTTPHeaderFields?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
|
||||
request.addValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
|
||||
task = session.dataTask(with: request)
|
||||
task?.resume()
|
||||
@@ -322,7 +314,6 @@ extension AudioStreamWorker: URLSessionDataDelegate {
|
||||
Log.monitor("\(task.currentRequest?.url?.absoluteString ?? "nil url") error: \(err.localizedDescription)")
|
||||
|
||||
let _ = doneCallback(id, err)
|
||||
return
|
||||
}
|
||||
|
||||
let shouldSave = doneCallback(id, nil)
|
||||
|
||||
@@ -37,14 +37,12 @@ public typealias UTC = Int
|
||||
public struct SALockScreenInfo {
|
||||
var title: String
|
||||
var artist: String
|
||||
var albumTitle: String?
|
||||
var artwork: UIImage?
|
||||
var releaseDate: UTC
|
||||
|
||||
public init(title: String, artist: String, albumTitle: String?, artwork: UIImage?, releaseDate: UTC) {
|
||||
public init(title: String, artist: String, artwork: UIImage?, releaseDate: UTC) {
|
||||
self.title = title
|
||||
self.artist = artist
|
||||
self.albumTitle = albumTitle
|
||||
self.artwork = artwork
|
||||
self.releaseDate = releaseDate
|
||||
}
|
||||
|
||||
+16
-51
@@ -45,15 +45,6 @@ public class SAPlayer {
|
||||
private var presenter: SAPlayerPresenter!
|
||||
private var player: AudioEngine?
|
||||
|
||||
/**
|
||||
Any necessary header fields for streaming and downloading requests can be set here.
|
||||
*/
|
||||
public var HTTPHeaderFields: [String: String]? {
|
||||
didSet {
|
||||
AudioDataManager.shared.setHTTPHeaderFields(HTTPHeaderFields)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Access the engine of the player. Engine is nil if player has not been initialized with audio.
|
||||
|
||||
@@ -132,6 +123,13 @@ public class SAPlayer {
|
||||
|
||||
node.rate = value
|
||||
playbackRateOfAudioChanged(rate: value)
|
||||
|
||||
// if skip silences was on, reset it to have the new rate
|
||||
// TODO fix this to rate being broadcasted and handled in only Features.SkipSilences https://github.com/tanhakabir/SwiftAudioPlayer/issues/77
|
||||
// if Features.SkipSilences.enabled && !(value == rate ?? 1.0 - 0.5 || value == rate ?? 1.0 + 0.5) {
|
||||
// _ = Features.SkipSilences.disable()
|
||||
// _ = Features.SkipSilences.enable()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +251,6 @@ public class SAPlayer {
|
||||
}
|
||||
|
||||
audioModifiers.append(AVAudioUnitTimePitch(audioComponentDescription: componentDescription))
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption), name: AVAudioSession.interruptionNotification, object: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,36 +290,6 @@ public class SAPlayer {
|
||||
func addUrlToMapping(url: URL) {
|
||||
presenter.addUrlToKeyMap(url)
|
||||
}
|
||||
|
||||
@objc func handleInterruption(notification: Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Switch over the interruption type.
|
||||
switch type {
|
||||
|
||||
case .began:
|
||||
// An interruption began. Update the UI as necessary.
|
||||
pause()
|
||||
|
||||
case .ended:
|
||||
// An interruption ended. Resume playback, if appropriate.
|
||||
|
||||
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
||||
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
||||
if options.contains(.shouldResume) {
|
||||
// An interruption ended. Resume playback.
|
||||
play()
|
||||
} else {
|
||||
// An interruption ended. Don't resume playback.
|
||||
}
|
||||
|
||||
default: ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum SAPlayerBitrate {
|
||||
@@ -449,9 +416,8 @@ extension SAPlayer {
|
||||
// This prevents a crash where an owning engine already exists.
|
||||
presenter.handleClear()
|
||||
|
||||
// order here matters, need to set media info before trying to play audio
|
||||
self.mediaInfo = mediaInfo
|
||||
presenter.handlePlaySavedAudio(withSavedUrl: url)
|
||||
self.mediaInfo = mediaInfo
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,9 +455,8 @@ extension SAPlayer {
|
||||
// This prevents a crash where an owning engine already exists.
|
||||
presenter.handleClear()
|
||||
|
||||
// order here matters, need to set media info before trying to play audio
|
||||
self.mediaInfo = mediaInfo
|
||||
presenter.handlePlayStreamedAudio(withRemoteUrl: url, bitrate: bitrate)
|
||||
self.mediaInfo = mediaInfo
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -533,22 +498,22 @@ extension SAPlayer {
|
||||
|
||||
//MARK: - Internal implementation of delegate
|
||||
extension SAPlayer: SAPlayerDelegate {
|
||||
internal func startAudioDownloaded(withSavedUrl url: AudioURL) {
|
||||
func startAudioDownloaded(withSavedUrl url: AudioURL) {
|
||||
player = AudioDiskEngine(withSavedUrl: url, delegate: presenter)
|
||||
}
|
||||
|
||||
internal func startAudioStreamed(withRemoteUrl url: AudioURL, bitrate: SAPlayerBitrate) {
|
||||
func startAudioStreamed(withRemoteUrl url: AudioURL, bitrate: SAPlayerBitrate) {
|
||||
player = AudioStreamEngine(withRemoteUrl: url, delegate: presenter, bitrate: bitrate)
|
||||
}
|
||||
|
||||
internal func clearEngine() {
|
||||
func clearEngine() {
|
||||
player?.pause()
|
||||
player?.invalidate()
|
||||
player = nil
|
||||
Log.info("cleared engine")
|
||||
}
|
||||
|
||||
internal func playEngine() {
|
||||
func playEngine() {
|
||||
becomeDeviceAudioPlayer()
|
||||
player?.play()
|
||||
}
|
||||
@@ -556,7 +521,7 @@ extension SAPlayer: SAPlayerDelegate {
|
||||
//Start taking control as the device's player
|
||||
private func becomeDeviceAudioPlayer() {
|
||||
do {
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
if #available(iOS 11.0, *) {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, policy: .longFormAudio, options: [])
|
||||
} else {
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode(rawValue: convertFromAVAudioSessionMode(AVAudioSession.Mode.default)), options: .allowAirPlay)
|
||||
@@ -567,11 +532,11 @@ extension SAPlayer: SAPlayerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
internal func pauseEngine() {
|
||||
func pauseEngine() {
|
||||
player?.pause()
|
||||
}
|
||||
|
||||
internal func seekEngine(toNeedle needle: Needle) {
|
||||
func seekEngine(toNeedle needle: Needle) {
|
||||
var seekToNeedle = needle < 0 ? 0 : needle
|
||||
seekToNeedle = needle > Needle(duration ?? 0) ? Needle(duration ?? 0) : needle
|
||||
player?.seek(toNeedle: seekToNeedle)
|
||||
|
||||
@@ -29,8 +29,7 @@ extension SAPlayer {
|
||||
/**
|
||||
Enable feature to skip silences in spoken word audio. The player will speed up the rate of audio playback when silence is detected. This can be called at any point of audio playback.
|
||||
|
||||
- Precondition: The first audio modifier must be the default `AVAudioUnitTimePitch` that comes with the SAPlayer for this feature to work.
|
||||
- Important: If you want to change the rate of the overall player while having skip silences on, please use `SAPlayer.Features.SkipSilences.setRateSafely()` to properly set the rate of the player. Any rate changes to the player will be ignored while using Skip Silences otherwise.
|
||||
- Important: The first audio modifier must be the default `AVAudioUnitTimePitch` that comes with the SAPlayer for this feature to work.
|
||||
*/
|
||||
public static func enable() -> Bool {
|
||||
guard let engine = SAPlayer.shared.engine else { return false }
|
||||
@@ -73,7 +72,7 @@ extension SAPlayer {
|
||||
/**
|
||||
Disable feature to skip silences in spoken word audio. The player will speed up the rate of audio playback when silence is detected. This can be called at any point of audio playback.
|
||||
|
||||
- Precondition: The first audio modifier must be the default `AVAudioUnitTimePitch` that comes with the SAPlayer for this feature to work.
|
||||
- Important: The first audio modifier must be the default `AVAudioUnitTimePitch` that comes with the SAPlayer for this feature to work.
|
||||
*/
|
||||
public static func disable() -> Bool {
|
||||
guard let engine = SAPlayer.shared.engine else { return false }
|
||||
@@ -84,16 +83,6 @@ extension SAPlayer {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
Use this function to set the overall rate of the player for when skip silences is on. This ensures that the overall rate will be what is set through this function even as skip silences is on; if this function is not used then any changes asked of from the overall player while skip silences is on won't be recorded!
|
||||
|
||||
- Important: The first audio modifier must be the default `AVAudioUnitTimePitch` that comes with the SAPlayer for this feature to work.
|
||||
*/
|
||||
public static func setRateSafely(_ rate: Float) {
|
||||
originalRate = rate
|
||||
SAPlayer.shared.rate = rate
|
||||
}
|
||||
|
||||
private static func scaledPower(power: Float) -> Float {
|
||||
guard power.isFinite else { return 0.0 }
|
||||
let minDb: Float = -80.0
|
||||
@@ -131,28 +120,5 @@ extension SAPlayer {
|
||||
timer?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
public struct Loop {
|
||||
static var enabled: Bool = false
|
||||
static var playingStatusId: UInt?
|
||||
|
||||
public static func enable() {
|
||||
enabled = true
|
||||
|
||||
guard playingStatusId == nil else { return }
|
||||
|
||||
playingStatusId = SAPlayer.Updates.PlayingStatus.subscribe({ (url, status) in
|
||||
if status == .ended && enabled {
|
||||
SAPlayer.shared.seekTo(seconds: 0.0)
|
||||
SAPlayer.shared.play()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public static func disable() {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '6.0.0'
|
||||
s.version = '5.0.3'
|
||||
s.summary = 'SwiftAudioPlayer is a Swift based audio player that can handle streaming from a remote location and audio manipulation.'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
@@ -26,9 +26,9 @@ SwiftAudioPlayer is a Swift based audio player that can handle streaming from a
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'tanhakabir' => 'tanhakabir.ca@gmail.com', 'JonMercer' => 'mercer.jon@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/tanhakabir/SwiftAudioPlayer.git', :tag => s.version.to_s }
|
||||
s.social_media_url = 'https://twitter.com/_tanhakabir'
|
||||
s.social_media_url = 'https://twitter.com/_tanhakabir'
|
||||
|
||||
s.platforms = { :ios => '10.0', :tvos => '10.0' }
|
||||
s.ios.deployment_target = '10.0'
|
||||
|
||||
s.source_files = 'Source/**/*'
|
||||
s.swift_version = '5.0'
|
||||
|
||||
Reference in New Issue
Block a user