Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f93bd1a86 | |||
| 20c0253f68 | |||
| 5e78c446a9 | |||
| 487b071490 | |||
| b79d16b409 | |||
| 4684a92380 | |||
| 2cff597e45 | |||
| 98dc7cfa3c | |||
| 4f1242f56d | |||
| f3c91ccc34 | |||
| 2d88b69aa7 | |||
| f67b939ac4 | |||
| a0e9b973e0 | |||
| ef54080a68 | |||
| 2d35bbad59 | |||
| 13b68920d1 | |||
| 2e8f44c553 | |||
| 58ac9b5ae5 | |||
| 706ab5961c | |||
| 50139ca8c5 | |||
| 6c3e52b66e | |||
| 6d955687a3 | |||
| 38d5740f4d | |||
| 4cbfb4b16b |
+4
-4
@@ -48,7 +48,7 @@
|
||||
A470FE2125F9AF1400F135FF /* AudioQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A470FE2025F9AF1400F135FF /* AudioQueue.swift */; };
|
||||
A4827771262A216C00B6918A /* StreamingDownloadDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4827770262A216C00B6918A /* StreamingDownloadDirector.swift */; };
|
||||
A4B4CC122223ED2A0045554B /* SAPlayerDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B4CC112223ED2A0045554B /* SAPlayerDownloader.swift */; };
|
||||
A4FBA6B5221B74C900D5A353 /* SALockScreenInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B3221B74C900D5A353 /* SALockScreenInfo.swift */; };
|
||||
A4FBA6B5221B74C900D5A353 /* SAPlayerHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B3221B74C900D5A353 /* SAPlayerHelpers.swift */; };
|
||||
A4FBA6B7221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */; };
|
||||
A4FBA6B9221BAF8700D5A353 /* SAAudioAvailabilityRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4FBA6B8221BAF8700D5A353 /* SAAudioAvailabilityRange.swift */; };
|
||||
E08AD6157EF688FE832F866CBCDA3532 /* SwiftAudioPlayer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FB83B3B4253D41C37C5563D34D450BF8 /* SwiftAudioPlayer-dummy.m */; };
|
||||
@@ -134,7 +134,7 @@
|
||||
A470FE2025F9AF1400F135FF /* AudioQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioQueue.swift; sourceTree = "<group>"; };
|
||||
A4827770262A216C00B6918A /* StreamingDownloadDirector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingDownloadDirector.swift; sourceTree = "<group>"; };
|
||||
A4B4CC112223ED2A0045554B /* SAPlayerDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerDownloader.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B3221B74C900D5A353 /* SALockScreenInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SALockScreenInfo.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B3221B74C900D5A353 /* SAPlayerHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerHelpers.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAPlayerUpdateSubscription.swift; sourceTree = "<group>"; };
|
||||
A4FBA6B8221BAF8700D5A353 /* SAAudioAvailabilityRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAAudioAvailabilityRange.swift; sourceTree = "<group>"; };
|
||||
AB41D88A2C694FBDF26EA56381EED25F /* Pods-SwiftAudioPlayer_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SwiftAudioPlayer_Example-dummy.m"; sourceTree = "<group>"; };
|
||||
@@ -342,7 +342,7 @@
|
||||
A4681FE2220117B50018AB51 /* Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A4FBA6B3221B74C900D5A353 /* SALockScreenInfo.swift */,
|
||||
A4FBA6B3221B74C900D5A353 /* SAPlayerHelpers.swift */,
|
||||
A4681F8D2200E00E0018AB51 /* SAPlayer.swift */,
|
||||
A411CE4525F9609D0039E1CD /* SAPlayerFeatures.swift */,
|
||||
A4FBA6B6221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift */,
|
||||
@@ -526,7 +526,7 @@
|
||||
A4681FCF220113A40018AB51 /* AudioConverterListener.swift in Sources */,
|
||||
A4681FE1220113E70018AB51 /* Constants.swift in Sources */,
|
||||
A40DBE292391D9CA00F86146 /* Data.swift in Sources */,
|
||||
A4FBA6B5221B74C900D5A353 /* SALockScreenInfo.swift in Sources */,
|
||||
A4FBA6B5221B74C900D5A353 /* SAPlayerHelpers.swift in Sources */,
|
||||
A4681FC6220113880018AB51 /* SAPlayer.swift in Sources */,
|
||||
A4FBA6B7221BAC3D00D5A353 /* SAPlayerUpdateSubscription.swift in Sources */,
|
||||
A4681FC72201138B0018AB51 /* SAPlayerDelegate.swift in Sources */,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<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">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -116,30 +116,32 @@
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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">
|
||||
<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">
|
||||
<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" 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"/>
|
||||
<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"/>
|
||||
<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" 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"/>
|
||||
<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"/>
|
||||
<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" fixedFrame="YES" 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" 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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -158,14 +160,22 @@
|
||||
<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"/>
|
||||
@@ -174,19 +184,27 @@
|
||||
<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"/>
|
||||
@@ -194,11 +212,13 @@
|
||||
<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"/>
|
||||
@@ -212,6 +232,7 @@
|
||||
<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, artwork: nil, releaseDate: self.releaseDate)
|
||||
return SALockScreenInfo(title: self.title, artist: self.artist, albumTitle: nil, artwork: nil, releaseDate: self.releaseDate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class ViewController: UIViewController {
|
||||
var isDownloading: Bool = false
|
||||
var isStreaming: Bool = false
|
||||
var beingSeeked: Bool = false
|
||||
var loopEnabled = false
|
||||
|
||||
|
||||
var downloadId: UInt?
|
||||
@@ -133,7 +134,7 @@ class ViewController: UIViewController {
|
||||
// unsubscribeFromChanges()
|
||||
// subscribeToChanges()
|
||||
|
||||
SAPlayer.shared.mediaInfo = SALockScreenInfo(title: selectedAudio.title, artist: selectedAudio.artist, artwork: UIImage(), releaseDate: selectedAudio.releaseDate)
|
||||
SAPlayer.shared.mediaInfo = SALockScreenInfo(title: selectedAudio.title, artist: selectedAudio.artist, albumTitle: nil, artwork: UIImage(), releaseDate: selectedAudio.releaseDate)
|
||||
}
|
||||
|
||||
func checkIfAudioDownloaded() {
|
||||
@@ -213,8 +214,10 @@ class ViewController: UIViewController {
|
||||
self.playPauseButton.setTitle("Loading", for: .normal)
|
||||
return
|
||||
case .ended:
|
||||
self.isPlayable = false
|
||||
self.playPauseButton.setTitle("Done", for: .normal)
|
||||
if !self.loopEnabled {
|
||||
self.isPlayable = false
|
||||
self.playPauseButton.setTitle("Done", for: .normal)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -336,16 +339,6 @@ 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()
|
||||
}
|
||||
|
||||
@@ -388,5 +381,18 @@ 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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ Thus, using [AudioToolbox](https://developer.apple.com/documentation/audiotoolbo
|
||||
These are community supported audio manipulation features using this audio engine. You can implement your own version of these features and you can look at [SAPlayerFeatures](https://github.com/tanhakabir/SwiftAudioPlayer/blob/master/Source/SAPlayerFeatures.swift) to learn how they were implemented using the library.
|
||||
1. Skip silences in audio
|
||||
1. Sleep timer to stop playing audio after a delay
|
||||
1. Loop audio playback for both streamed and saved audio
|
||||
|
||||
### Requirements
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ 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,6 +45,7 @@ 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)!
|
||||
|
||||
@@ -102,6 +103,8 @@ class AudioEngine: AudioEngineProtocol {
|
||||
AudioClockDirector.shared.changeInAudioBuffered(key, buffered: bufferedSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
private var audioModifiers: [AVAudioUnit]?
|
||||
|
||||
init(url: AudioURL, delegate:AudioEngineDelegate?, engineAudioFormat: AVAudioFormat) {
|
||||
self.key = url.key
|
||||
@@ -115,35 +118,35 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
func initHelper(_ engineAudioFormat: AVAudioFormat) {
|
||||
engine.attach(playerNode)
|
||||
|
||||
for node in SAPlayer.shared.audioModifiers {
|
||||
engine.attach(node)
|
||||
}
|
||||
|
||||
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 {
|
||||
audioModifiers = SAPlayer.shared.audioModifiers
|
||||
|
||||
defer { engine.prepare() }
|
||||
|
||||
guard let audioModifiers = audioModifiers, audioModifiers.count > 0 else {
|
||||
engine.connect(playerNode, to: engine.mainMixerNode, format: engineAudioFormat)
|
||||
return
|
||||
}
|
||||
|
||||
engine.prepare()
|
||||
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)
|
||||
i += 1
|
||||
}
|
||||
|
||||
let finalNode = audioModifiers[audioModifiers.count - 1]
|
||||
|
||||
engine.connect(finalNode, to: engine.mainMixerNode, format: engineAudioFormat)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -173,7 +176,7 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { [weak self] (timer: Timer) in
|
||||
guard let self = self else { return }
|
||||
guard self.playingStatus != .ended else {
|
||||
guard !self.engineInvalidated else {
|
||||
self.delegate = nil
|
||||
return
|
||||
}
|
||||
@@ -194,8 +197,6 @@ class AudioEngine: AudioEngineProtocol {
|
||||
|
||||
let isPlaying = engine.isRunning && playerNode.isPlaying
|
||||
playingStatus = isPlaying ? .playing : .paused
|
||||
|
||||
// playingStatus = .paused
|
||||
}
|
||||
|
||||
func play() {
|
||||
@@ -231,7 +232,12 @@ class AudioEngine: AudioEngineProtocol {
|
||||
}
|
||||
|
||||
func invalidate() {
|
||||
engineInvalidated = true
|
||||
playerNode.stop()
|
||||
engine.stop()
|
||||
|
||||
if let audioModifiers = audioModifiers, audioModifiers.count > 0 {
|
||||
audioModifiers.forEach { engine.detach($0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +74,18 @@ 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +170,6 @@ class AudioStreamEngine: AudioEngine {
|
||||
|
||||
doRepeatedly(timeInterval: timeInterval) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard self.playingStatus != .ended else { return }
|
||||
|
||||
self.repeatedUpdates()
|
||||
}
|
||||
@@ -328,7 +333,13 @@ class AudioStreamEngine: AudioEngine {
|
||||
}
|
||||
|
||||
override func invalidate() {
|
||||
queue.sync { [weak self] in
|
||||
self?.invalidateHelperDispatchQueue()
|
||||
self?.converter.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private func invalidateHelperDispatchQueue() {
|
||||
super.invalidate()
|
||||
converter.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public struct SAAudioAvailabilityRange {
|
||||
var needleAtEnd = false
|
||||
|
||||
if(totalDurationBuffered > 0 && needle > 0) {
|
||||
needleAtEnd = needle >= totalDurationBuffered - 1
|
||||
needleAtEnd = needle >= totalDurationBuffered - 5
|
||||
}
|
||||
|
||||
// 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, *)
|
||||
@available(iOS 10.0, tvOS 10.0, *)
|
||||
func setLockScreenInfo(withMediaInfo info: SALockScreenInfo?, duration: Duration) {
|
||||
var nowPlayingInfo:[String : Any] = [:]
|
||||
|
||||
@@ -50,6 +50,7 @@ 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?
|
||||
@@ -57,7 +58,7 @@ extension LockScreenViewProtocol {
|
||||
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = title
|
||||
nowPlayingInfo[MPMediaItemPropertyArtist] = artist
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = artist
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = albumTitle
|
||||
//nowPlayingInfo[MPMediaItemPropertyGenre] = //maybe later when we have it
|
||||
//nowPlayingInfo[MPMediaItemPropertyIsExplicit] = //maybe later when we have it
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumArtist] = artist
|
||||
@@ -168,7 +169,10 @@ extension LockScreenViewProtocol {
|
||||
}
|
||||
|
||||
func updateLockscreenSkipIntervals() {
|
||||
MPRemoteCommandCenter.shared().skipBackwardCommand.preferredIntervals = [skipBackwardSeconds] as [NSNumber]
|
||||
MPRemoteCommandCenter.shared().skipForwardCommand.preferredIntervals = [skipForwardSeconds] as [NSNumber]
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +130,8 @@ class AudioStreamWorker:NSObject, AudioDataStreamable {
|
||||
var request = URLRequest(url: url)
|
||||
HTTPHeaderFields?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
|
||||
task = session.dataTask(with: request)
|
||||
|
||||
task?.resume()
|
||||
|
||||
task?.taskDescription = id
|
||||
task?.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +322,7 @@ extension AudioStreamWorker: URLSessionDataDelegate {
|
||||
Log.monitor("\(task.currentRequest?.url?.absoluteString ?? "nil url") error: \(err.localizedDescription)")
|
||||
|
||||
let _ = doneCallback(id, err)
|
||||
return
|
||||
}
|
||||
|
||||
let shouldSave = doneCallback(id, nil)
|
||||
|
||||
+28
-15
@@ -180,15 +180,9 @@ public class SAPlayer {
|
||||
public var audioModifiers: [AVAudioUnit] = []
|
||||
|
||||
/**
|
||||
List of audio URLs queued for playback.
|
||||
List of queued audio for playback. You can edit this list as you wish to modify the queue.
|
||||
*/
|
||||
public var audioQueued: [URL] {
|
||||
get {
|
||||
return presenter.audioQueue.map { (queued) -> URL in
|
||||
return queued.url
|
||||
}
|
||||
}
|
||||
}
|
||||
public var audioQueued: [SAAudioQueueItem] = []
|
||||
|
||||
/**
|
||||
Total duration of current audio initialized. Returns nil if no audio is initialized in player.
|
||||
@@ -513,7 +507,7 @@ extension SAPlayer {
|
||||
}
|
||||
|
||||
/**
|
||||
Queues saved audio to be played next. The URLs in the queuecan be both remote or on disk but once the queued audio starts playing it will start buffering and loading then. This means no guarantee for a 'gapless' playback where there might be several moments in between one audio ending and another starting due to buffering remote audio.
|
||||
Queues saved audio to be played next. The URLs in the queue can be both remote or on disk but once the queued audio starts playing it will start buffering and loading then. This means no guarantee for a 'gapless' playback where there might be several moments in between one audio ending and another starting due to buffering remote audio.
|
||||
|
||||
- Parameter withSavedUrl: The URL of the audio saved on the device.
|
||||
- Parameter mediaInfo: The media information of the audio to show on the lockscreen media player (optional).
|
||||
@@ -522,6 +516,25 @@ extension SAPlayer {
|
||||
presenter.handleQueueSavedAudio(withSavedUrl: url, mediaInfo: mediaInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the first queued audio if one exists. Receive the first URL removed back.
|
||||
|
||||
- Returns the URL of the removed audio.
|
||||
*/
|
||||
public func removeFirstQueuedAudio() -> URL? {
|
||||
guard audioQueued.count != 0 else { return nil }
|
||||
return presenter.handleRemoveFirstQueuedItem()
|
||||
}
|
||||
|
||||
/**
|
||||
Clear the list of queued audio.
|
||||
|
||||
- Returns the list of removed audio URLs
|
||||
*/
|
||||
public func clearAllQueuedAudio() -> [URL] {
|
||||
return presenter.handleClearQueued()
|
||||
}
|
||||
|
||||
/**
|
||||
Resets the player to the state before initializing audio and setting media info.
|
||||
*/
|
||||
@@ -533,22 +546,22 @@ extension SAPlayer {
|
||||
|
||||
//MARK: - Internal implementation of delegate
|
||||
extension SAPlayer: SAPlayerDelegate {
|
||||
func startAudioDownloaded(withSavedUrl url: AudioURL) {
|
||||
internal func startAudioDownloaded(withSavedUrl url: AudioURL) {
|
||||
player = AudioDiskEngine(withSavedUrl: url, delegate: presenter)
|
||||
}
|
||||
|
||||
func startAudioStreamed(withRemoteUrl url: AudioURL, bitrate: SAPlayerBitrate) {
|
||||
internal func startAudioStreamed(withRemoteUrl url: AudioURL, bitrate: SAPlayerBitrate) {
|
||||
player = AudioStreamEngine(withRemoteUrl: url, delegate: presenter, bitrate: bitrate)
|
||||
}
|
||||
|
||||
func clearEngine() {
|
||||
internal func clearEngine() {
|
||||
player?.pause()
|
||||
player?.invalidate()
|
||||
player = nil
|
||||
Log.info("cleared engine")
|
||||
}
|
||||
|
||||
func playEngine() {
|
||||
internal func playEngine() {
|
||||
becomeDeviceAudioPlayer()
|
||||
player?.play()
|
||||
}
|
||||
@@ -567,11 +580,11 @@ extension SAPlayer: SAPlayerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func pauseEngine() {
|
||||
internal func pauseEngine() {
|
||||
player?.pause()
|
||||
}
|
||||
|
||||
func seekEngine(toNeedle needle: Needle) {
|
||||
internal func seekEngine(toNeedle needle: Needle) {
|
||||
var seekToNeedle = needle < 0 ? 0 : needle
|
||||
seekToNeedle = needle > Needle(duration ?? 0) ? Needle(duration ?? 0) : needle
|
||||
player?.seek(toNeedle: seekToNeedle)
|
||||
|
||||
@@ -131,5 +131,37 @@ extension SAPlayer {
|
||||
timer?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Feature to play the current playing audio on repeat until feature is disabled.
|
||||
*/
|
||||
public struct Loop {
|
||||
static var enabled: Bool = false
|
||||
static var playingStatusId: UInt?
|
||||
|
||||
/**
|
||||
Enable feature to play the current playing audio on loop. This will continue until the feature is disabled. And this feature works for both remote and saved audio.
|
||||
*/
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Disable feature playing audio on loop.
|
||||
*/
|
||||
public static func disable() {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,49 @@ 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, artwork: UIImage?, releaseDate: UTC) {
|
||||
public init(title: String, artist: String, albumTitle: String?, artwork: UIImage?, releaseDate: UTC) {
|
||||
self.title = title
|
||||
self.artist = artist
|
||||
self.albumTitle = albumTitle
|
||||
self.artwork = artwork
|
||||
self.releaseDate = releaseDate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Use to add audio to be queued for playback.
|
||||
*/
|
||||
public struct SAAudioQueueItem {
|
||||
var loc: Location
|
||||
var url: URL
|
||||
var mediaInfo: SALockScreenInfo?
|
||||
var bitrate: SAPlayerBitrate
|
||||
|
||||
/**
|
||||
Use to add audio to be queued for playback.
|
||||
|
||||
- Parameter loc: If the URL for the file is remote or saved on device.
|
||||
- Parameter url: URL of audio to be queued
|
||||
- Parameter mediaInfo: Relevant lockscreen media info for the queued audio.
|
||||
- Parameter bitrate: For streamed remote audio specifiy a bitrate if different from high. Use low bitrate for radio streams.
|
||||
*/
|
||||
init(loc: Location, url: URL, mediaInfo: SALockScreenInfo?, bitrate: SAPlayerBitrate = .high) {
|
||||
self.loc = loc
|
||||
self.url = url
|
||||
self.mediaInfo = mediaInfo
|
||||
self.bitrate = bitrate
|
||||
}
|
||||
|
||||
/**
|
||||
Where the queued audio is sourced. Remote to be streamed or locally saved on device.
|
||||
*/
|
||||
enum Location {
|
||||
case remote
|
||||
case saved
|
||||
}
|
||||
}
|
||||
@@ -28,25 +28,6 @@ import AVFoundation
|
||||
import MediaPlayer
|
||||
|
||||
class SAPlayerPresenter {
|
||||
struct QueueItem {
|
||||
var loc: Location
|
||||
var url: URL
|
||||
var mediaInfo: SALockScreenInfo?
|
||||
var bitrate: SAPlayerBitrate
|
||||
|
||||
init(loc: Location, url: URL, mediaInfo: SALockScreenInfo?, bitrate: SAPlayerBitrate = .high) {
|
||||
self.loc = loc
|
||||
self.url = url
|
||||
self.mediaInfo = mediaInfo
|
||||
self.bitrate = bitrate
|
||||
}
|
||||
}
|
||||
|
||||
enum Location {
|
||||
case remote
|
||||
case disk
|
||||
}
|
||||
|
||||
weak var delegate: SAPlayerDelegate?
|
||||
var shouldPlayImmediately = false //for auto-play
|
||||
|
||||
@@ -61,7 +42,7 @@ class SAPlayerPresenter {
|
||||
var durationRef:UInt = 0
|
||||
var needleRef:UInt = 0
|
||||
var playingStatusRef:UInt = 0
|
||||
var audioQueue: [QueueItem] = []
|
||||
var audioQueue: [SAAudioQueueItem] = []
|
||||
|
||||
init(delegate: SAPlayerDelegate?) {
|
||||
self.delegate = delegate
|
||||
@@ -102,11 +83,28 @@ class SAPlayerPresenter {
|
||||
}
|
||||
|
||||
func handleQueueStreamedAudio(withRemoteUrl url: URL, mediaInfo: SALockScreenInfo?, bitrate: SAPlayerBitrate) {
|
||||
audioQueue.append(QueueItem(loc: .remote, url: url, mediaInfo: mediaInfo, bitrate: bitrate))
|
||||
audioQueue.append(SAAudioQueueItem(loc: .remote, url: url, mediaInfo: mediaInfo, bitrate: bitrate))
|
||||
}
|
||||
|
||||
func handleQueueSavedAudio(withSavedUrl url: URL, mediaInfo: SALockScreenInfo?) {
|
||||
audioQueue.append(QueueItem(loc: .disk, url: url, mediaInfo: mediaInfo))
|
||||
audioQueue.append(SAAudioQueueItem(loc: .saved, url: url, mediaInfo: mediaInfo))
|
||||
}
|
||||
|
||||
func handleRemoveFirstQueuedItem() -> URL? {
|
||||
guard audioQueue.count != 0 else { return nil }
|
||||
|
||||
return audioQueue.remove(at: 0).url
|
||||
}
|
||||
|
||||
func handleClearQueued() -> [URL] {
|
||||
guard audioQueue.count != 0 else { return [] }
|
||||
|
||||
let urls = audioQueue.map { item in
|
||||
return item.url
|
||||
}
|
||||
|
||||
audioQueue = []
|
||||
return urls
|
||||
}
|
||||
|
||||
private func attachForUpdates(url: URL) {
|
||||
@@ -256,7 +254,7 @@ extension SAPlayerPresenter {
|
||||
case .remote:
|
||||
self.handlePlayStreamedAudio(withRemoteUrl: nextAudioURL.url, bitrate: nextAudioURL.bitrate)
|
||||
break
|
||||
case .disk:
|
||||
case .saved:
|
||||
self.handlePlaySavedAudio(withSavedUrl: nextAudioURL.url)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '6.0.0'
|
||||
s.version = '6.3.1'
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user