Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 876d517f3d | |||
| 0a12c68274 | |||
| 873e537301 |
@@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" 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="14460.20"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -22,48 +20,50 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lTK-Hd-Tl2">
|
||||
<rect key="frame" x="16" y="320" width="343" height="2"/>
|
||||
<rect key="frame" x="16" y="303" width="343" height="2"/>
|
||||
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="progressTintColor" red="0.46202266219999999" green="0.83828371759999998" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="trackTintColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</progressView>
|
||||
<slider opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="749" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="w2a-RA-zmI">
|
||||
<rect key="frame" x="14" y="305" width="347" height="31"/>
|
||||
<rect key="frame" x="14" y="288" width="347" height="31"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="maximumTrackTintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="scrubberSeeked:" destination="vXZ-lx-hvc" eventType="valueChanged" id="jDA-wR-wxk"/>
|
||||
<action selector="beingScrubberSeeked:" destination="vXZ-lx-hvc" eventType="touchDown" id="UXg-Wf-fKv"/>
|
||||
<action selector="scrubberSeeked:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="hTi-fq-lrl"/>
|
||||
<action selector="scrubberSeeked:" destination="vXZ-lx-hvc" eventType="touchUpOutside" id="mFP-SW-38w"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jUc-tP-CC5">
|
||||
<rect key="frame" x="172.5" y="250" width="30" height="30"/>
|
||||
<rect key="frame" x="172.5" y="233" width="30" height="30"/>
|
||||
<state key="normal" title="play"/>
|
||||
<connections>
|
||||
<action selector="playPauseTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="Avk-K3-EZ7"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tFH-sY-Xu9">
|
||||
<rect key="frame" x="62.5" y="250" width="30" height="30"/>
|
||||
<rect key="frame" x="62.5" y="233" width="30" height="30"/>
|
||||
<state key="normal" title="-15"/>
|
||||
<connections>
|
||||
<action selector="skipBackwardTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="PCT-BE-udf"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0QE-3F-a4G">
|
||||
<rect key="frame" x="282.5" y="250" width="30" height="30"/>
|
||||
<rect key="frame" x="282.5" y="233" width="30" height="30"/>
|
||||
<state key="normal" title="+30"/>
|
||||
<connections>
|
||||
<action selector="skipForwardTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="uXv-bz-tnt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.10000000000000001" maxValue="32" translatesAutoresizingMaskIntoConstraints="NO" id="vfk-OJ-S3T">
|
||||
<rect key="frame" x="14" y="564" width="347" height="31"/>
|
||||
<rect key="frame" x="14" y="447" width="347" height="31"/>
|
||||
<connections>
|
||||
<action selector="rateChanged:" destination="vXZ-lx-hvc" eventType="valueChanged" id="FDJ-jA-bm8"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="joK-xi-MCo">
|
||||
<rect key="frame" x="16" y="80" width="343" height="29"/>
|
||||
<rect key="frame" x="16" y="60" width="343" height="32"/>
|
||||
<segments>
|
||||
<segment title="Soundbite"/>
|
||||
<segment title="Acquired"/>
|
||||
@@ -74,39 +74,39 @@
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KDu-ea-kF8">
|
||||
<rect key="frame" x="78" y="140" width="69" height="30"/>
|
||||
<rect key="frame" x="78" y="123" width="69" height="30"/>
|
||||
<state key="normal" title="Download"/>
|
||||
<connections>
|
||||
<action selector="downloadTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="8Jg-1C-0Ms"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6d9-Bc-hIz">
|
||||
<rect key="frame" x="244" y="140" width="49" height="30"/>
|
||||
<rect key="frame" x="244" y="123" width="49" height="30"/>
|
||||
<state key="normal" title="Stream"/>
|
||||
<connections>
|
||||
<action selector="streamTouched:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="AXY-N7-87Y"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="rate: 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yUQ-mI-ozK">
|
||||
<rect key="frame" x="157" y="535" width="61" height="21"/>
|
||||
<rect key="frame" x="157" y="418" width="61" 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="0:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="j3w-gr-HzF">
|
||||
<rect key="frame" x="16" y="297" width="27" height="15"/>
|
||||
<rect key="frame" x="16" y="280" width="27" height="15"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="100:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Urj-Dv-41y">
|
||||
<rect key="frame" x="319" y="297" width="40" height="15"/>
|
||||
<rect key="frame" x="319" y="280" width="40" height="15"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="remote url: " textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1IX-z5-wWx">
|
||||
<rect key="frame" x="16" y="207" width="343" height="16"/>
|
||||
<rect key="frame" x="16" y="190" width="343" height="16"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -125,7 +125,7 @@
|
||||
<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="yUQ-mI-ozK" firstAttribute="top" secondItem="w2a-RA-zmI" secondAttribute="bottom" constant="200" id="K1K-8N-SpD"/>
|
||||
<constraint firstItem="yUQ-mI-ozK" firstAttribute="top" secondItem="w2a-RA-zmI" secondAttribute="bottom" constant="100" id="K1K-8N-SpD"/>
|
||||
<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="78" id="SRU-sX-z5b"/>
|
||||
|
||||
@@ -79,6 +79,7 @@ class ViewController: UIViewController {
|
||||
|
||||
var isDownloading: Bool = false
|
||||
var isStreaming: Bool = false
|
||||
var beingSeeked: Bool = false
|
||||
|
||||
var duration: Double = 0.0
|
||||
|
||||
@@ -113,13 +114,15 @@ class ViewController: UIViewController {
|
||||
|
||||
_ = SAPlayer.Updates.ElapsedTime.subscribe { [weak self] (url, position) in
|
||||
guard let self = self else { return }
|
||||
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
|
||||
|
||||
self.currentTimestampLabel.text = SAPlayer.prettifyTimestamp(position)
|
||||
|
||||
guard self.duration != 0 else { return }
|
||||
|
||||
self.scrubberSlider.value = Float(position/self.duration)
|
||||
if self.beingSeeked == false{
|
||||
guard url == self.selectedAudio.url || url == self.savedUrls[self.selectedAudio] else { return }
|
||||
|
||||
self.currentTimestampLabel.text = SAPlayer.prettifyTimestamp(position)
|
||||
|
||||
guard self.duration != 0 else { return }
|
||||
|
||||
self.scrubberSlider.value = Float(position/self.duration)
|
||||
}
|
||||
}
|
||||
|
||||
_ = SAPlayer.Updates.AudioDownloading.subscribe { [weak self] (url, progress) in
|
||||
@@ -178,9 +181,15 @@ class ViewController: UIViewController {
|
||||
|
||||
// if let savedUrl = savedUrls[selectedAudio] {}
|
||||
}
|
||||
@IBAction func beingScrubberSeeked(_ sender: UISlider) {
|
||||
beingSeeked = true
|
||||
}
|
||||
|
||||
@IBAction func scrubberSeeked(_ sender: Any) {
|
||||
SAPlayer.shared.seekTo(seconds: Double(scrubberSlider.value))
|
||||
let value = Double(scrubberSlider.value) * duration
|
||||
SAPlayer.shared.seekTo(seconds: value)
|
||||
beingSeeked = false
|
||||
SAPlayer.shared.play()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ protocol AudioThrottleable {
|
||||
func tellAudioFormatFound()
|
||||
func tellByteOffset(offset: UInt64)
|
||||
func tellSeek(offset: UInt64)
|
||||
func tellBytesPerAudioPacket(count: UInt64)
|
||||
func pollRangeOfBytesAvailable() -> (UInt64, UInt64)
|
||||
func invalidate()
|
||||
}
|
||||
@@ -98,15 +99,7 @@ class AudioThrottler: AudioThrottleable {
|
||||
var byteOffsetBecauseOfSeek: UInt = 0
|
||||
var totalBytesExpected: Int64? //this got sent up twice. Once at beginning of stream and second from network seek. We honor the first send
|
||||
|
||||
var largestPollingOffsetDifference: UInt64 = 0
|
||||
var lastOffsetPolled: UInt64 = 0 {
|
||||
didSet {
|
||||
let diff = lastOffsetPolled - oldValue
|
||||
if diff > largestPollingOffsetDifference {
|
||||
largestPollingOffsetDifference = diff
|
||||
}
|
||||
}
|
||||
}
|
||||
var largestPollingOffsetDifference: UInt64 = 1
|
||||
|
||||
required init(withRemoteUrl url: AudioURL, withDelegate delegate: AudioThrottleDelegate) {
|
||||
self.url = url
|
||||
@@ -145,9 +138,14 @@ class AudioThrottler: AudioThrottleable {
|
||||
shouldThrottle = true //the above layer has enough info that we can throttle
|
||||
}
|
||||
|
||||
func tellBytesPerAudioPacket(count: UInt64) {
|
||||
if count > largestPollingOffsetDifference {
|
||||
largestPollingOffsetDifference = count
|
||||
}
|
||||
}
|
||||
|
||||
func tellByteOffset(offset: UInt64) {
|
||||
Log.debug("offset \(offset)")
|
||||
lastOffsetPolled = offset
|
||||
|
||||
for wrappedNetworkData in networkData {
|
||||
if wrappedNetworkData.containsOffset(UInt(offset)) {
|
||||
|
||||
@@ -98,7 +98,14 @@ class AudioParser: AudioParsable {
|
||||
return predictedCount
|
||||
}
|
||||
|
||||
var sumOfParsedAudioBytes:UInt32 = 0
|
||||
var sumOfParsedAudioBytes:UInt32 = 0 {
|
||||
didSet {
|
||||
if let byteCount = averageBytesPerPacket {
|
||||
throttler.tellBytesPerAudioPacket(count: UInt64(byteCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var numberOfPacketsParsed:UInt32 = 0
|
||||
var audioPackets: [(AudioStreamPacketDescription?,Data)] = [] {
|
||||
didSet {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioPlayer'
|
||||
s.version = '1.1.0'
|
||||
s.version = '1.1.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