Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42767039eb | |||
| 4d3c1db059 | |||
| b6510ca858 | |||
| 4ed73418a3 | |||
| 5b1594da9b | |||
| 0890671ec5 | |||
| 4bd0251fac | |||
| 1fe9b0da35 | |||
| 546dd29838 | |||
| 790d7b655b | |||
| ba12380126 | |||
| 4543c58f38 | |||
| a574f94c6b | |||
| 1e64a9aa8b | |||
| e969fd5550 |
@@ -1,2 +1,16 @@
|
||||
ignore:
|
||||
- "Example/.*"
|
||||
- "Tests/.*"
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# https://docs.codecov.com/docs/commit-status#informational
|
||||
informational: true
|
||||
target: 78%
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
target: 78%
|
||||
github_checks:
|
||||
annotations: false
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
name: validate
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -12,12 +13,17 @@ jobs:
|
||||
runs-on: blaze/macos-14
|
||||
strategy:
|
||||
matrix:
|
||||
destination: ["platform=iOS Simulator,name=iPhone 15 Pro"]
|
||||
target: [macos]
|
||||
include:
|
||||
- target: macos
|
||||
destination: '-destination "platform=macOS,name=Any Mac"'
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Run Tests
|
||||
run: |-
|
||||
xcodebuild test -scheme SwiftAudioEx -destination "${destination}" -enableCodeCoverage YES
|
||||
env:
|
||||
destination: ${{ matrix.destination }}
|
||||
run: xcodebuild test -scheme SwiftAudioEx ${{ matrix.destination }} -enableCodeCoverage YES
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.target == 'macos'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -3,99 +3,92 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
070713072067EB4F00F789B3 /* Double + Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070713062067EB4F00F789B3 /* Double + Extensions.swift */; };
|
||||
070713092067EFFB00F789B3 /* AudioController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070713082067EFFB00F789B3 /* AudioController.swift */; };
|
||||
0707130B2067F2E000F789B3 /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0707130A2067F2E000F789B3 /* QueueViewController.swift */; };
|
||||
0707130F2067F40A00F789B3 /* QueueTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0707130D2067F40A00F789B3 /* QueueTableViewCell.swift */; };
|
||||
070713102067F40A00F789B3 /* QueueTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0707130E2067F40A00F789B3 /* QueueTableViewCell.xib */; };
|
||||
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 */; };
|
||||
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
|
||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
|
||||
9B1D5E2027C76F6F004CA883 /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */; };
|
||||
9B88195D2BC8657A00E20DCE /* SwiftAudioApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B88195C2BC8657A00E20DCE /* SwiftAudioApp.swift */; };
|
||||
9B8819612BC8657B00E20DCE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B8819602BC8657B00E20DCE /* Assets.xcassets */; };
|
||||
9B8819652BC8657B00E20DCE /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B8819642BC8657B00E20DCE /* Preview Assets.xcassets */; };
|
||||
9B8819712BC866A300E20DCE /* AudioController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B88196C2BC866A300E20DCE /* AudioController.swift */; };
|
||||
9B8819742BC866A300E20DCE /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B88196F2BC866A300E20DCE /* Extensions.swift */; };
|
||||
9B8819752BC866A300E20DCE /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8819702BC866A300E20DCE /* PlayerView.swift */; };
|
||||
9B8819782BC866E800E20DCE /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B8819772BC866E800E20DCE /* SwiftAudioEx */; };
|
||||
9B88197A2BC9883200E20DCE /* PlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B8819792BC9883200E20DCE /* PlayerViewModel.swift */; };
|
||||
9B88197C2BC98F5000E20DCE /* QueueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B88197B2BC98F5000E20DCE /* QueueView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
070713062067EB4F00F789B3 /* Double + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double + Extensions.swift"; sourceTree = "<group>"; };
|
||||
070713082067EFFB00F789B3 /* AudioController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioController.swift; sourceTree = "<group>"; };
|
||||
0707130A2067F2E000F789B3 /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
|
||||
0707130D2067F40A00F789B3 /* QueueTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTableViewCell.swift; sourceTree = "<group>"; };
|
||||
0707130E2067F40A00F789B3 /* QueueTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QueueTableViewCell.xib; sourceTree = "<group>"; };
|
||||
607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAudio_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>"; };
|
||||
607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
9B1D5E1C27C76F49004CA883 /* SwiftAudioEx */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftAudioEx; path = ..; sourceTree = "<group>"; };
|
||||
9B8819592BC8657A00E20DCE /* SwiftAudio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAudio.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9B88195C2BC8657A00E20DCE /* SwiftAudioApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftAudioApp.swift; sourceTree = "<group>"; };
|
||||
9B8819602BC8657B00E20DCE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
9B8819622BC8657B00E20DCE /* SwiftAudio.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftAudio.entitlements; sourceTree = "<group>"; };
|
||||
9B8819642BC8657B00E20DCE /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
9B88196B2BC865E100E20DCE /* SwiftAudioEx */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftAudioEx; path = ..; sourceTree = "<group>"; };
|
||||
9B88196C2BC866A300E20DCE /* AudioController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioController.swift; sourceTree = "<group>"; };
|
||||
9B88196F2BC866A300E20DCE /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||
9B8819702BC866A300E20DCE /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
|
||||
9B8819792BC9883200E20DCE /* PlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewModel.swift; sourceTree = "<group>"; };
|
||||
9B88197B2BC98F5000E20DCE /* QueueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
607FACCD1AFB9204008FA782 /* Frameworks */ = {
|
||||
9B8819562BC8657A00E20DCE /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9B1D5E2027C76F6F004CA883 /* SwiftAudioEx in Frameworks */,
|
||||
9B8819782BC866E800E20DCE /* SwiftAudioEx in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
607FACC71AFB9204008FA782 = {
|
||||
9B8819502BC8657A00E20DCE = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD21AFB9204008FA782 /* Example for SwiftAudio */,
|
||||
607FACD11AFB9204008FA782 /* Products */,
|
||||
9B05AA2F2660276400C7A389 /* Frameworks */,
|
||||
9B88196B2BC865E100E20DCE /* SwiftAudioEx */,
|
||||
9B88195B2BC8657A00E20DCE /* SwiftAudio */,
|
||||
9B88195A2BC8657A00E20DCE /* Products */,
|
||||
9B8819762BC866E800E20DCE /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACD11AFB9204008FA782 /* Products */ = {
|
||||
9B88195A2BC8657A00E20DCE /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */,
|
||||
9B8819592BC8657A00E20DCE /* SwiftAudio.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACD21AFB9204008FA782 /* Example for SwiftAudio */ = {
|
||||
9B88195B2BC8657A00E20DCE /* SwiftAudio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD51AFB9204008FA782 /* AppDelegate.swift */,
|
||||
070713082067EFFB00F789B3 /* AudioController.swift */,
|
||||
607FACD71AFB9204008FA782 /* ViewController.swift */,
|
||||
0707130A2067F2E000F789B3 /* QueueViewController.swift */,
|
||||
070713062067EB4F00F789B3 /* Double + Extensions.swift */,
|
||||
0707130D2067F40A00F789B3 /* QueueTableViewCell.swift */,
|
||||
0707130E2067F40A00F789B3 /* QueueTableViewCell.xib */,
|
||||
607FACD91AFB9204008FA782 /* Main.storyboard */,
|
||||
607FACDC1AFB9204008FA782 /* Images.xcassets */,
|
||||
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
|
||||
607FACD31AFB9204008FA782 /* Supporting Files */,
|
||||
9B88196C2BC866A300E20DCE /* AudioController.swift */,
|
||||
9B88196F2BC866A300E20DCE /* Extensions.swift */,
|
||||
9B8819792BC9883200E20DCE /* PlayerViewModel.swift */,
|
||||
9B8819702BC866A300E20DCE /* PlayerView.swift */,
|
||||
9B88195C2BC8657A00E20DCE /* SwiftAudioApp.swift */,
|
||||
9B88197B2BC98F5000E20DCE /* QueueView.swift */,
|
||||
9B8819602BC8657B00E20DCE /* Assets.xcassets */,
|
||||
9B8819622BC8657B00E20DCE /* SwiftAudio.entitlements */,
|
||||
9B8819632BC8657B00E20DCE /* Preview Content */,
|
||||
);
|
||||
name = "Example for SwiftAudio";
|
||||
path = SwiftAudio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACD31AFB9204008FA782 /* Supporting Files */ = {
|
||||
9B8819632BC8657B00E20DCE /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD41AFB9204008FA782 /* Info.plist */,
|
||||
9B8819642BC8657B00E20DCE /* Preview Assets.xcassets */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9B05AA2F2660276400C7A389 /* Frameworks */ = {
|
||||
9B8819762BC866E800E20DCE /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9B1D5E1C27C76F49004CA883 /* SwiftAudioEx */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -103,131 +96,106 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
607FACCF1AFB9204008FA782 /* SwiftAudio_Example */ = {
|
||||
9B8819582BC8657A00E20DCE /* SwiftAudio */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAudio_Example" */;
|
||||
buildConfigurationList = 9B8819682BC8657B00E20DCE /* Build configuration list for PBXNativeTarget "SwiftAudio" */;
|
||||
buildPhases = (
|
||||
607FACCC1AFB9204008FA782 /* Sources */,
|
||||
607FACCD1AFB9204008FA782 /* Frameworks */,
|
||||
607FACCE1AFB9204008FA782 /* Resources */,
|
||||
9B8819552BC8657A00E20DCE /* Sources */,
|
||||
9B8819562BC8657A00E20DCE /* Frameworks */,
|
||||
9B8819572BC8657A00E20DCE /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SwiftAudio_Example;
|
||||
name = SwiftAudio;
|
||||
packageProductDependencies = (
|
||||
9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */,
|
||||
9B8819772BC866E800E20DCE /* SwiftAudioEx */,
|
||||
);
|
||||
productName = SwiftAudio;
|
||||
productReference = 607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */;
|
||||
productReference = 9B8819592BC8657A00E20DCE /* SwiftAudio.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
607FACC81AFB9204008FA782 /* Project object */ = {
|
||||
9B8819512BC8657A00E20DCE /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0830;
|
||||
LastUpgradeCheck = 1010;
|
||||
ORGANIZATIONNAME = CocoaPods;
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1530;
|
||||
LastUpgradeCheck = 1530;
|
||||
TargetAttributes = {
|
||||
607FACCF1AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
LastSwiftMigration = 1020;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
9B8819582BC8657A00E20DCE = {
|
||||
CreatedOnToolsVersion = 15.3;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAudio" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
buildConfigurationList = 9B8819542BC8657A00E20DCE /* Build configuration list for PBXProject "SwiftAudio" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 607FACC71AFB9204008FA782;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
|
||||
mainGroup = 9B8819502BC8657A00E20DCE;
|
||||
productRefGroup = 9B88195A2BC8657A00E20DCE /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
607FACCF1AFB9204008FA782 /* SwiftAudio_Example */,
|
||||
9B8819582BC8657A00E20DCE /* SwiftAudio */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
607FACCE1AFB9204008FA782 /* Resources */ = {
|
||||
9B8819572BC8657A00E20DCE /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
|
||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
|
||||
070713102067F40A00F789B3 /* QueueTableViewCell.xib in Resources */,
|
||||
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
|
||||
9B8819652BC8657B00E20DCE /* Preview Assets.xcassets in Resources */,
|
||||
9B8819612BC8657B00E20DCE /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
607FACCC1AFB9204008FA782 /* Sources */ = {
|
||||
9B8819552BC8657A00E20DCE /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0707130B2067F2E000F789B3 /* QueueViewController.swift in Sources */,
|
||||
070713072067EB4F00F789B3 /* Double + Extensions.swift in Sources */,
|
||||
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
|
||||
0707130F2067F40A00F789B3 /* QueueTableViewCell.swift in Sources */,
|
||||
070713092067EFFB00F789B3 /* AudioController.swift in Sources */,
|
||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
|
||||
9B8819742BC866A300E20DCE /* Extensions.swift in Sources */,
|
||||
9B8819752BC866A300E20DCE /* PlayerView.swift in Sources */,
|
||||
9B8819712BC866A300E20DCE /* AudioController.swift in Sources */,
|
||||
9B88197A2BC9883200E20DCE /* PlayerViewModel.swift in Sources */,
|
||||
9B88197C2BC98F5000E20DCE /* QueueView.swift in Sources */,
|
||||
9B88195D2BC8657A00E20DCE /* SwiftAudioApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
607FACD91AFB9204008FA782 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
607FACDA1AFB9204008FA782 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
607FACDF1AFB9204008FA782 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
607FACED1AFB9204008FA782 /* Debug */ = {
|
||||
9B8819662BC8657B00E20DCE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
@@ -236,17 +204,19 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
@@ -254,35 +224,39 @@
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
607FACEE1AFB9204008FA782 /* Release */ = {
|
||||
9B8819672BC8657B00E20DCE /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
@@ -291,17 +265,19 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
@@ -309,70 +285,106 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
MTL_FAST_MATH = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
607FACF01AFB9204008FA782 /* Debug */ = {
|
||||
9B8819692BC8657B00E20DCE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = SwiftAudio/SwiftAudio.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SwiftAudio/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 7U2TUNKNQX;
|
||||
INFOPLIST_FILE = SwiftAudio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.doublesymmetry.demo.--PRODUCT-NAME-rfc1034identifier-";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.doublesymmetry.SwiftAudio;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
607FACF11AFB9204008FA782 /* Release */ = {
|
||||
9B88196A2BC8657B00E20DCE /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = SwiftAudio/SwiftAudio.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SwiftAudio/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 7U2TUNKNQX;
|
||||
INFOPLIST_FILE = SwiftAudio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.doublesymmetry.demo.--PRODUCT-NAME-rfc1034identifier-";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.doublesymmetry.SwiftAudio;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAudio" */ = {
|
||||
9B8819542BC8657A00E20DCE /* Build configuration list for PBXProject "SwiftAudio" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
607FACED1AFB9204008FA782 /* Debug */,
|
||||
607FACEE1AFB9204008FA782 /* Release */,
|
||||
9B8819662BC8657B00E20DCE /* Debug */,
|
||||
9B8819672BC8657B00E20DCE /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAudio_Example" */ = {
|
||||
9B8819682BC8657B00E20DCE /* Build configuration list for PBXNativeTarget "SwiftAudio" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
607FACF01AFB9204008FA782 /* Debug */,
|
||||
607FACF11AFB9204008FA782 /* Release */,
|
||||
9B8819692BC8657B00E20DCE /* Debug */,
|
||||
9B88196A2BC8657B00E20DCE /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
@@ -380,11 +392,11 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */ = {
|
||||
9B8819772BC866E800E20DCE /* SwiftAudioEx */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SwiftAudioEx;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 607FACC81AFB9204008FA782 /* Project object */;
|
||||
rootObject = 9B8819512BC8657A00E20DCE /* Project object */;
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftAudio_Example.app"
|
||||
BlueprintName = "SwiftAudio_Example"
|
||||
ReferencedContainer = "container:SwiftAudio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
||||
BuildableName = "SwiftAudio_Tests.xctest"
|
||||
BlueprintName = "SwiftAudio_Tests"
|
||||
ReferencedContainer = "container:SwiftAudio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftAudio_Example.app"
|
||||
BlueprintName = "SwiftAudio_Example"
|
||||
ReferencedContainer = "container:SwiftAudio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
||||
BuildableName = "SwiftAudio_Tests.xctest"
|
||||
BlueprintName = "SwiftAudio_Tests"
|
||||
ReferencedContainer = "container:SwiftAudio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftAudio_Example.app"
|
||||
BlueprintName = "SwiftAudio_Example"
|
||||
ReferencedContainer = "container:SwiftAudio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "SwiftAudio_Example.app"
|
||||
BlueprintName = "SwiftAudio_Example"
|
||||
ReferencedContainer = "container:SwiftAudio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 03/11/2018.
|
||||
// Copyright (c) 2018 Jørgen Henrichsen. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
application.beginReceivingRemoteControlEvents()
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "22AMillion.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cover.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -9,20 +9,18 @@
|
||||
import Foundation
|
||||
import SwiftAudioEx
|
||||
|
||||
|
||||
class AudioController {
|
||||
|
||||
static let shared = AudioController()
|
||||
let player: QueuedAudioPlayer
|
||||
let audioSessionController = AudioSessionController.shared
|
||||
|
||||
let sources: [AudioItem] = [
|
||||
DefaultAudioItem(audioUrl: "https://rntp.dev/example/Longing.mp3", artist: "David Chavez", title: "Longing", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://rntp.dev/example/Soul%20Searching.mp3", artist: "David Chavez", title: "Soul Searching (Demo)", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://rntp.dev/example/Lullaby%20(Demo).mp3", artist: "David Chavez", title: "Lullaby (Demo)", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://rntp.dev/example/Lullaby%20(Demo).mp3", artist: "David Chavez", title: "Lullaby (Demo)", sourceType: .stream, artwork: #imageLiteral(resourceName: "cover")),
|
||||
DefaultAudioItem(audioUrl: "https://rntp.dev/example/Rhythm%20City%20(Demo).mp3", artist: "David Chavez", title: "Rhythm City (Demo)", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://rntp.dev/example/hls/whip/playlist.m3u8", title: "Whip", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://ais-sa5.cdnstream1.com/b75154_128mp3", artist: "New York, NY", title: "Smooth Jazz 24/7", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
DefaultAudioItem(audioUrl: "https://ais-sa5.cdnstream1.com/b75154_128mp3", artist: "New York, NY", title: "Smooth Jazz 24/7", sourceType: .stream, artwork: #imageLiteral(resourceName: "cover")),
|
||||
DefaultAudioItem(audioUrl: "https://traffic.libsyn.com/atpfm/atp545.mp3", title: "Chapters", sourceType: .stream, artwork: #imageLiteral(resourceName: "22AMI")),
|
||||
]
|
||||
|
||||
@@ -38,7 +36,7 @@ class AudioController {
|
||||
.previous,
|
||||
.changePlaybackPosition
|
||||
]
|
||||
try? audioSessionController.set(category: .playback)
|
||||
|
||||
player.repeatMode = .queue
|
||||
DispatchQueue.main.async {
|
||||
self.player.add(items: self.sources)
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 CocoaPods. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SwiftAudio" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,208 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" 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>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="ufC-wZ-h7g">
|
||||
<objects>
|
||||
<viewController id="vXZ-lx-hvc" customClass="ViewController" customModule="SwiftAudio_Example" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="jyV-Pf-zRb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="RX3-VR-CL6">
|
||||
<rect key="frame" x="32" y="533" width="311" height="34"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9Q1-U9-TUC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="103.5" height="34"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Prev"/>
|
||||
<connections>
|
||||
<action selector="previous:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="fFb-iW-sFr"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EOo-zV-6l2">
|
||||
<rect key="frame" x="103.5" y="0.0" width="104" height="34"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="togglePlay:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="oYu-xi-n6T"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Nhf-qB-91A">
|
||||
<rect key="frame" x="207.5" y="0.0" width="103.5" height="34"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Next"/>
|
||||
<connections>
|
||||
<action selector="next:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="Tha-3J-gVM"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="34" id="T4q-HG-vqM"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="l9B-hM-Ajc">
|
||||
<rect key="frame" x="302" y="20" width="57" height="34"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
|
||||
<state key="normal" title="Queue"/>
|
||||
<connections>
|
||||
<segue destination="vDz-qW-uY8" kind="presentation" identifier="QueueSegue" id="eke-1c-Fsm"/>
|
||||
</connections>
|
||||
</button>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="FCd-3e-22D">
|
||||
<rect key="frame" x="67.5" y="84" width="240" height="240"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="240" id="5Sj-BZ-sg4"/>
|
||||
<constraint firstAttribute="height" constant="240" id="Hij-Yw-6Lg"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3CL-8o-zYW">
|
||||
<rect key="frame" x="16" y="462" width="39" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="RVb-HZ-QCX">
|
||||
<rect key="frame" x="320" y="462" width="39" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="RWN-If-dGG">
|
||||
<rect key="frame" x="14" y="424" width="347" height="31"/>
|
||||
<color key="tintColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="maximumTrackTintColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="thumbTintColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<action selector="scrubbing:" destination="vXZ-lx-hvc" eventType="touchUpOutside" id="HeH-aB-VXZ"/>
|
||||
<action selector="scrubbing:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="NfP-3T-dnw"/>
|
||||
<action selector="scrubbingValueChanged:" destination="vXZ-lx-hvc" eventType="valueChanged" id="MLD-nW-rXm"/>
|
||||
<action selector="startScrubbing:" destination="vXZ-lx-hvc" eventType="touchDown" id="lD9-dR-QTO"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dfk-yr-rwm">
|
||||
<rect key="frame" x="16" y="354" width="343" height="21.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
|
||||
<color key="textColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Artist" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T7Y-1Q-7UU">
|
||||
<rect key="frame" x="16" y="379.5" width="343" height="19.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="thin" pointSize="16"/>
|
||||
<color key="textColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="1ML-yD-9Rf">
|
||||
<rect key="frame" x="177.5" y="587" width="20" height="20"/>
|
||||
</activityIndicatorView>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="ErrorText" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iCe-6A-2My">
|
||||
<rect key="frame" x="158.5" y="588.5" width="58.5" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="T7Y-1Q-7UU" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" id="0eh-sL-186"/>
|
||||
<constraint firstItem="iCe-6A-2My" firstAttribute="centerY" secondItem="1ML-yD-9Rf" secondAttribute="centerY" id="4Fp-kE-AAg"/>
|
||||
<constraint firstItem="l9B-hM-Ajc" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="54L-0h-0ba"/>
|
||||
<constraint firstItem="l9B-hM-Ajc" firstAttribute="top" secondItem="jyV-Pf-zRb" secondAttribute="bottom" id="9Uh-K9-988"/>
|
||||
<constraint firstItem="RVb-HZ-QCX" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="BhV-UD-qhh"/>
|
||||
<constraint firstItem="iCe-6A-2My" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="Dhm-Bn-wZH"/>
|
||||
<constraint firstItem="FCd-3e-22D" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="GhI-f1-DkR"/>
|
||||
<constraint firstItem="T7Y-1Q-7UU" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="HoH-i0-yof"/>
|
||||
<constraint firstItem="RWN-If-dGG" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" id="Nw7-WM-LFd"/>
|
||||
<constraint firstItem="RX3-VR-CL6" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="O0h-NL-iXW"/>
|
||||
<constraint firstItem="1ML-yD-9Rf" firstAttribute="top" secondItem="EOo-zV-6l2" secondAttribute="bottom" constant="20" id="Uop-aD-I5b"/>
|
||||
<constraint firstItem="dfk-yr-rwm" firstAttribute="top" secondItem="FCd-3e-22D" secondAttribute="bottom" constant="30" id="W4w-6K-AW8"/>
|
||||
<constraint firstItem="RWN-If-dGG" firstAttribute="top" secondItem="T7Y-1Q-7UU" secondAttribute="bottom" constant="25" id="XgV-XL-QCL"/>
|
||||
<constraint firstItem="dfk-yr-rwm" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" id="YUE-uf-Rp1"/>
|
||||
<constraint firstItem="RVb-HZ-QCX" firstAttribute="top" secondItem="RWN-If-dGG" secondAttribute="bottom" constant="8" id="ZkD-u2-Zbr"/>
|
||||
<constraint firstItem="T7Y-1Q-7UU" firstAttribute="top" secondItem="dfk-yr-rwm" secondAttribute="bottom" constant="4" id="baR-zV-tgo"/>
|
||||
<constraint firstItem="RWN-If-dGG" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="eNt-u9-qot"/>
|
||||
<constraint firstItem="1ML-yD-9Rf" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="fdl-RK-Hq8"/>
|
||||
<constraint firstItem="RX3-VR-CL6" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" constant="16" id="hEd-b2-Ggo"/>
|
||||
<constraint firstItem="FCd-3e-22D" firstAttribute="top" secondItem="l9B-hM-Ajc" secondAttribute="bottom" constant="30" id="ikz-ZP-jNM"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="RX3-VR-CL6" secondAttribute="trailing" constant="16" id="kSP-Mq-R5P"/>
|
||||
<constraint firstItem="dfk-yr-rwm" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailingMargin" id="m6u-7a-ffF"/>
|
||||
<constraint firstItem="3CL-8o-zYW" firstAttribute="top" secondItem="RWN-If-dGG" secondAttribute="bottom" constant="8" id="sGK-bn-zxD"/>
|
||||
<constraint firstItem="2fi-mo-0CV" firstAttribute="top" secondItem="RX3-VR-CL6" secondAttribute="bottom" constant="100" id="vd2-dd-hVu"/>
|
||||
<constraint firstItem="3CL-8o-zYW" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leadingMargin" id="wOy-Rx-rvK"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="artistLabel" destination="T7Y-1Q-7UU" id="b5S-lt-PqG"/>
|
||||
<outlet property="elapsedTimeLabel" destination="3CL-8o-zYW" id="7Wg-7X-Vrd"/>
|
||||
<outlet property="errorLabel" destination="iCe-6A-2My" id="T4b-0b-wdM"/>
|
||||
<outlet property="imageView" destination="FCd-3e-22D" id="gKL-za-haV"/>
|
||||
<outlet property="loadIndicator" destination="1ML-yD-9Rf" id="Xes-Ag-vhg"/>
|
||||
<outlet property="playButton" destination="EOo-zV-6l2" id="2d1-ad-s1k"/>
|
||||
<outlet property="remainingTimeLabel" destination="RVb-HZ-QCX" id="8hp-CK-XjF"/>
|
||||
<outlet property="slider" destination="RWN-If-dGG" id="Yxw-Gf-bR3"/>
|
||||
<outlet property="titleLabel" destination="dfk-yr-rwm" id="Hk3-m5-IOi"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="117.59999999999999" y="118.29085457271366"/>
|
||||
</scene>
|
||||
<!--Queue View Controller-->
|
||||
<scene sceneID="5Fm-oE-9Zc">
|
||||
<objects>
|
||||
<viewController id="vDz-qW-uY8" customClass="QueueViewController" customModule="SwiftAudio_Example" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="kv3-s6-lb0"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Fhe-7w-8BG"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="y7Y-Gm-oyZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dzA-9p-ejh">
|
||||
<rect key="frame" x="310" y="20" width="49" height="34"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeButton:" destination="vDz-qW-uY8" eventType="touchUpInside" id="0TB-bG-he7"/>
|
||||
</connections>
|
||||
</button>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="HPi-Pd-J9K">
|
||||
<rect key="frame" x="0.0" y="74" width="375" height="593"/>
|
||||
<color key="backgroundColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="1" green="0.1857388616" blue="0.57339501380000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="HPi-Pd-J9K" secondAttribute="trailing" id="CdI-lT-19N"/>
|
||||
<constraint firstItem="Fhe-7w-8BG" firstAttribute="top" secondItem="HPi-Pd-J9K" secondAttribute="bottom" id="Gb9-C1-ajx"/>
|
||||
<constraint firstItem="HPi-Pd-J9K" firstAttribute="leading" secondItem="y7Y-Gm-oyZ" secondAttribute="leading" id="aN2-LD-yxR"/>
|
||||
<constraint firstItem="HPi-Pd-J9K" firstAttribute="top" secondItem="dzA-9p-ejh" secondAttribute="bottom" constant="20" id="aSx-t1-T3e"/>
|
||||
<constraint firstItem="dzA-9p-ejh" firstAttribute="top" secondItem="kv3-s6-lb0" secondAttribute="bottom" id="nAL-i2-VQS"/>
|
||||
<constraint firstAttribute="trailing" secondItem="dzA-9p-ejh" secondAttribute="trailing" constant="16" id="qrg-S3-JJ2"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="HPi-Pd-J9K" id="P8P-at-xLc"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="zk4-9r-5Oh" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="917.60000000000002" y="117.39130434782609"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,15 +1,13 @@
|
||||
//
|
||||
// Double + Extensions.swift
|
||||
// SwiftAudio_Example
|
||||
// Extensions.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 25/03/2018.
|
||||
// Copyright © 2018 CocoaPods. All rights reserved.
|
||||
// Created by Brandon Sneed on 3/30/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Double {
|
||||
|
||||
private var formatter: DateComponentsFormatter {
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.allowedUnits = [.minute, .second]
|
||||
@@ -21,5 +19,4 @@ extension Double {
|
||||
func secondsToString() -> String {
|
||||
return formatter.string(from: self) ?? ""
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// PlayerView.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Brandon Sneed on 3/30/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftAudioEx
|
||||
|
||||
struct PlayerView: View {
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
@State private var showingQueue = false
|
||||
|
||||
let controller = AudioController.shared
|
||||
|
||||
init(viewModel: PlayerView.ViewModel = ViewModel()) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
Button(action: { showingQueue.toggle() }, label: {
|
||||
Text("Queue")
|
||||
.fontWeight(.bold)
|
||||
})
|
||||
}
|
||||
|
||||
if let image = viewModel.artwork {
|
||||
#if os(macOS)
|
||||
Image(nsImage: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 240, height: 240)
|
||||
.padding(.top, 30)
|
||||
#elseif os(iOS)
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 240, height: 240)
|
||||
.padding(.top, 30)
|
||||
#endif
|
||||
} else {
|
||||
AsyncImage(url: nil)
|
||||
.frame(width: 240, height: 240)
|
||||
.padding(.top, 30)
|
||||
}
|
||||
|
||||
VStack(spacing: 4) {
|
||||
Text(viewModel.title)
|
||||
.fontWeight(.semibold)
|
||||
.font(.system(size: 18))
|
||||
Text(viewModel.artist)
|
||||
.fontWeight(.thin)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
|
||||
if viewModel.maxTime > 0 {
|
||||
VStack {
|
||||
Slider(value: $viewModel.position, in: 0...viewModel.maxTime) { editing in
|
||||
viewModel.isScrubbing = editing
|
||||
print("scrubbing = \(viewModel.isScrubbing)")
|
||||
if viewModel.isScrubbing == false {
|
||||
controller.player.seek(to: viewModel.position)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text(viewModel.elapsedTime)
|
||||
.font(.system(size: 14))
|
||||
Spacer()
|
||||
Text(viewModel.remainingTime)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.padding(.top, 25)
|
||||
} else {
|
||||
Text("Live Stream")
|
||||
.padding(.top, 35)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button(action: controller.player.previous, label: {
|
||||
Text("Prev")
|
||||
.font(.system(size: 14))
|
||||
})
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button(action: {
|
||||
if viewModel.playing {
|
||||
controller.player.pause()
|
||||
} else {
|
||||
controller.player.play()
|
||||
}
|
||||
}, label: {
|
||||
Text(!viewModel.playWhenReady || viewModel.playbackState == .failed ? "Play" : "Pause")
|
||||
.font(.system(size: 18))
|
||||
.fontWeight(.semibold)
|
||||
})
|
||||
|
||||
.frame(maxWidth: .infinity)
|
||||
Button(action: controller.player.next, label: {
|
||||
Text("Next")
|
||||
.font(.system(size: 14))
|
||||
})
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.top, 80)
|
||||
|
||||
VStack {
|
||||
if viewModel.playbackState == .failed {
|
||||
Text("Playback failed.")
|
||||
.font(.system(size: 14))
|
||||
.foregroundStyle(.red)
|
||||
.padding(.top, 20)
|
||||
} else if (viewModel.playbackState == .loading || viewModel.playbackState == .buffering) && viewModel.playWhenReady {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.controlSize(.small)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.sheet(isPresented: $showingQueue) {
|
||||
QueueView()
|
||||
#if os(macOS)
|
||||
.frame(width: 300, height: 400)
|
||||
#endif
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Standard") {
|
||||
let viewModel = PlayerView.ViewModel()
|
||||
viewModel.title = "Longing"
|
||||
viewModel.artist = "David Chavez"
|
||||
|
||||
return PlayerView(viewModel: viewModel)
|
||||
}
|
||||
|
||||
#Preview("Error") {
|
||||
let viewModel = PlayerView.ViewModel()
|
||||
viewModel.title = "Longing"
|
||||
viewModel.artist = "David Chavez"
|
||||
viewModel.playbackState = .failed
|
||||
|
||||
return PlayerView(viewModel: viewModel)
|
||||
}
|
||||
|
||||
#Preview("Buffering") {
|
||||
let viewModel = PlayerView.ViewModel()
|
||||
viewModel.title = "Longing"
|
||||
viewModel.artist = "David Chavez"
|
||||
viewModel.playbackState = .buffering
|
||||
viewModel.playWhenReady = true
|
||||
|
||||
return PlayerView(viewModel: viewModel)
|
||||
}
|
||||
|
||||
#Preview("Live Stream") {
|
||||
let viewModel = PlayerView.ViewModel()
|
||||
viewModel.title = "Longing"
|
||||
viewModel.artist = "David Chavez"
|
||||
viewModel.maxTime = 0
|
||||
|
||||
return PlayerView(viewModel: viewModel)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// PlayerViewModel.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by David Chavez on 4/12/24.
|
||||
//
|
||||
|
||||
import SwiftAudioEx
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
public typealias NativeImage = NSImage
|
||||
#elseif os(iOS)
|
||||
import UIKit
|
||||
public typealias NativeImage = UIImage
|
||||
#endif
|
||||
|
||||
extension PlayerView {
|
||||
final class ViewModel: ObservableObject {
|
||||
// MARK: - Observables
|
||||
|
||||
@Published var playing: Bool = false
|
||||
@Published var position: Double = 0
|
||||
@Published var artwork: NativeImage? = nil
|
||||
@Published var title: String = ""
|
||||
@Published var artist: String = ""
|
||||
@Published var maxTime: TimeInterval = 100
|
||||
@Published var isScrubbing: Bool = false
|
||||
@Published var elapsedTime: String = "00:00"
|
||||
@Published var remainingTime: String = "00:00"
|
||||
|
||||
@Published var playWhenReady: Bool = false
|
||||
@Published var playbackState: AudioPlayerState = .idle
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
let controller = AudioController.shared
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
init() {
|
||||
controller.player.event.playWhenReadyChange.addListener(self, handlePlayWhenReadyChange)
|
||||
controller.player.event.stateChange.addListener(self, handleAudioPlayerStateChange)
|
||||
controller.player.event.playbackEnd.addListener(self, handleAudioPlayerPlaybackEnd(data:))
|
||||
controller.player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed)
|
||||
controller.player.event.seek.addListener(self, handleAudioPlayerDidSeek)
|
||||
controller.player.event.updateDuration.addListener(self, handleAudioPlayerUpdateDuration)
|
||||
controller.player.event.didRecreateAVPlayer.addListener(self, handleAVPlayerRecreated)
|
||||
}
|
||||
|
||||
// MARK: - Updates
|
||||
|
||||
private func render() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
playing = (controller.player.playerState == .playing)
|
||||
playbackState = controller.player.playerState
|
||||
playWhenReady = controller.player.playWhenReady
|
||||
position = controller.player.currentTime
|
||||
maxTime = controller.player.duration
|
||||
artist = controller.player.currentItem?.getArtist() ?? ""
|
||||
title = controller.player.currentItem?.getTitle() ?? ""
|
||||
elapsedTime = controller.player.currentTime.secondsToString()
|
||||
remainingTime = (controller.player.duration - controller.player.currentTime).secondsToString()
|
||||
if let item = controller.player.currentItem as? DefaultAudioItem {
|
||||
artwork = item.artwork
|
||||
} else {
|
||||
artwork = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func renderTimes() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
position = controller.player.currentTime
|
||||
maxTime = controller.player.duration
|
||||
elapsedTime = controller.player.currentTime.secondsToString()
|
||||
remainingTime = (controller.player.duration - controller.player.currentTime).secondsToString()
|
||||
print(elapsedTime)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AudioPlayer Event Handlers
|
||||
|
||||
func handleAudioPlayerStateChange(data: AudioPlayer.StateChangeEventData) {
|
||||
print("state=\(data)")
|
||||
render()
|
||||
}
|
||||
|
||||
func handlePlayWhenReadyChange(data: AudioPlayer.PlayWhenReadyChangeData) {
|
||||
print("playWhenReady=\(data)")
|
||||
render()
|
||||
}
|
||||
|
||||
func handleAudioPlayerPlaybackEnd(data: AudioPlayer.PlaybackEndEventData) {
|
||||
print("playEndReason=\(data)")
|
||||
}
|
||||
|
||||
func handleAudioPlayerSecondElapsed(data: AudioPlayer.SecondElapseEventData) {
|
||||
if !isScrubbing {
|
||||
renderTimes()
|
||||
}
|
||||
}
|
||||
|
||||
func handleAudioPlayerDidSeek(data: AudioPlayer.SeekEventData) {
|
||||
// .. don't need this
|
||||
}
|
||||
|
||||
func handleAudioPlayerUpdateDuration(data: AudioPlayer.UpdateDurationEventData) {
|
||||
if !isScrubbing {
|
||||
renderTimes()
|
||||
}
|
||||
}
|
||||
|
||||
func handleAVPlayerRecreated() {
|
||||
// .. don't need this
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
//
|
||||
// QueueTableViewCell.swift
|
||||
// SwiftAudio_Example
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 25/03/2018.
|
||||
// Copyright © 2018 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class QueueTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var artistLabel: UILabel!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="QueueTableViewCell" customModule="SwiftAudio_Example" customModuleProvider="target"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="QueueTableViewCell" customModule="SwiftAudio_Example" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="79.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="R0I-g7-ETn">
|
||||
<rect key="frame" x="16" y="16" width="343" height="19.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
|
||||
<color key="textColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Artist" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jRU-3B-2pA">
|
||||
<rect key="frame" x="16" y="43.5" width="343" height="19.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="16"/>
|
||||
<color key="textColor" red="0.99999600649999998" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="R0I-g7-ETn" firstAttribute="trailing" secondItem="H2p-sc-9uM" secondAttribute="trailingMargin" id="8gl-XI-iAW"/>
|
||||
<constraint firstItem="jRU-3B-2pA" firstAttribute="trailing" secondItem="H2p-sc-9uM" secondAttribute="trailingMargin" id="A7F-XO-H0i"/>
|
||||
<constraint firstItem="jRU-3B-2pA" firstAttribute="top" secondItem="R0I-g7-ETn" secondAttribute="bottom" constant="8" id="Jdu-e3-Oeq"/>
|
||||
<constraint firstItem="R0I-g7-ETn" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="VNU-d7-G4N"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="jRU-3B-2pA" secondAttribute="bottom" constant="6" id="nBr-J4-PUM"/>
|
||||
<constraint firstItem="R0I-g7-ETn" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="5" id="tE6-pp-JML"/>
|
||||
<constraint firstItem="jRU-3B-2pA" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="z3F-hI-GcC"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="artistLabel" destination="jRU-3B-2pA" id="IVV-n5-wmt"/>
|
||||
<outlet property="titleLabel" destination="R0I-g7-ETn" id="ICg-6a-6vz"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="34.5" y="54"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// QueueView.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by David Chavez on 4/12/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftAudioEx
|
||||
|
||||
struct QueueView: View {
|
||||
let controller = AudioController.shared
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
List {
|
||||
if controller.player.currentItem != nil {
|
||||
Section(header: Text("Playing Now")) {
|
||||
QueueItemView(
|
||||
title: controller.player.currentItem?.getTitle() ?? "",
|
||||
artist: controller.player.currentItem?.getArtist() ?? ""
|
||||
)
|
||||
}
|
||||
}
|
||||
Section(header: Text("Up Next")) {
|
||||
ForEach(controller.player.nextItems as! [DefaultAudioItem]) { item in
|
||||
QueueItemView(
|
||||
title: item.getTitle() ?? "",
|
||||
artist: item.getArtist() ?? ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Queue")
|
||||
.toolbar {
|
||||
Button("Close") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct QueueItemView: View {
|
||||
let title: String
|
||||
let artist: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text(title)
|
||||
.fontWeight(.semibold)
|
||||
Text(artist)
|
||||
.fontWeight(.light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#Preview {
|
||||
QueueView()
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
//
|
||||
// QueueViewController.swift
|
||||
// SwiftAudio_Example
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 25/03/2018.
|
||||
// Copyright © 2018 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftAudioEx
|
||||
|
||||
|
||||
class QueueViewController: UIViewController {
|
||||
|
||||
let controller = AudioController.shared
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
|
||||
let cellReuseId: String = "QueueCell"
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableView.register(UINib.init(nibName: "QueueTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: cellReuseId)
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
}
|
||||
|
||||
@IBAction func closeButton(_ sender: UIButton) {
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension QueueViewController: UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 0:
|
||||
return 1
|
||||
case 1:
|
||||
return controller.player.nextItems.count
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) as! QueueTableViewCell
|
||||
|
||||
let item: AudioItem?
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
item = controller.player.currentItem
|
||||
case 1:
|
||||
item = controller.player.nextItems[indexPath.row]
|
||||
default:
|
||||
item = nil
|
||||
}
|
||||
|
||||
if let item = item {
|
||||
cell.titleLabel.text = item.getTitle()
|
||||
cell.artistLabel.text = item.getArtist()
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
switch section {
|
||||
case 0: return "Playing Now"
|
||||
case 1: return "Up Next"
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// SwiftAudioApp.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Brandon Sneed on 3/30/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct SwiftAudioApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
PlayerView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 03/11/2018.
|
||||
// Copyright (c) 2018 Jørgen Henrichsen. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftAudioEx
|
||||
import AVFoundation
|
||||
import MediaPlayer
|
||||
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var playButton: UIButton!
|
||||
@IBOutlet weak var slider: UISlider!
|
||||
@IBOutlet weak var imageView: UIImageView!
|
||||
@IBOutlet weak var remainingTimeLabel: UILabel!
|
||||
@IBOutlet weak var elapsedTimeLabel: UILabel!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var artistLabel: UILabel!
|
||||
@IBOutlet weak var loadIndicator: UIActivityIndicatorView!
|
||||
@IBOutlet weak var errorLabel: UILabel!
|
||||
|
||||
private var isScrubbing: Bool = false
|
||||
private let controller = AudioController.shared
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
controller.player.event.playWhenReadyChange.addListener(self, handlePlayWhenReadyChange)
|
||||
controller.player.event.stateChange.addListener(self, handleAudioPlayerStateChange)
|
||||
controller.player.event.playbackEnd.addListener(self, handleAudioPlayerPlaybackEnd(data:))
|
||||
controller.player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed)
|
||||
controller.player.event.seek.addListener(self, handleAudioPlayerDidSeek)
|
||||
controller.player.event.updateDuration.addListener(self, handleAudioPlayerUpdateDuration)
|
||||
controller.player.event.didRecreateAVPlayer.addListener(self, handleAVPlayerRecreated)
|
||||
handleAudioPlayerStateChange(data: controller.player.playerState)
|
||||
DispatchQueue.main.async {
|
||||
self.render()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func togglePlay(_ sender: Any) {
|
||||
if !controller.audioSessionController.audioSessionIsActive {
|
||||
try? controller.audioSessionController.activateSession()
|
||||
}
|
||||
controller.player.playWhenReady = playButton.currentTitle == "Play"
|
||||
}
|
||||
|
||||
@IBAction func previous(_ sender: Any) {
|
||||
controller.player.previous()
|
||||
}
|
||||
|
||||
@IBAction func next(_ sender: Any) {
|
||||
controller.player.next()
|
||||
}
|
||||
|
||||
@IBAction func startScrubbing(_ sender: UISlider) {
|
||||
isScrubbing = true
|
||||
}
|
||||
|
||||
@IBAction func scrubbing(_ sender: UISlider) {
|
||||
controller.player.seek(to: Double(slider.value))
|
||||
}
|
||||
|
||||
@IBAction func scrubbingValueChanged(_ sender: UISlider) {
|
||||
let value = Double(slider.value)
|
||||
elapsedTimeLabel.text = value.secondsToString()
|
||||
remainingTimeLabel.text = (controller.player.duration - value).secondsToString()
|
||||
}
|
||||
|
||||
// MARK: - Render
|
||||
|
||||
func renderTimeValues() {
|
||||
self.slider.maximumValue = Float(self.controller.player.duration)
|
||||
self.slider.setValue(Float(self.controller.player.currentTime), animated: true)
|
||||
self.elapsedTimeLabel.text = self.controller.player.currentTime.secondsToString()
|
||||
self.remainingTimeLabel.text = (self.controller.player.duration - self.controller.player.currentTime).secondsToString()
|
||||
}
|
||||
|
||||
func render() {
|
||||
let player = self.controller.player
|
||||
|
||||
// Render play button
|
||||
self.playButton.setTitle(
|
||||
!player.playWhenReady || player.playerState == .failed
|
||||
? "Play"
|
||||
: "Pause",
|
||||
for: .normal
|
||||
)
|
||||
|
||||
// Render metadata
|
||||
if let item = player.currentItem {
|
||||
self.titleLabel.text = item.getTitle()
|
||||
self.artistLabel.text = item.getArtist()
|
||||
item.getArtwork({ (image) in
|
||||
self.imageView.image = image
|
||||
})
|
||||
}
|
||||
|
||||
// Render time values
|
||||
self.renderTimeValues()
|
||||
|
||||
// Render error label
|
||||
if (player.playerState == .failed) {
|
||||
self.errorLabel.isHidden = false
|
||||
self.errorLabel.text = "Playback failed."
|
||||
} else {
|
||||
self.errorLabel.text = ""
|
||||
self.errorLabel.isHidden = true
|
||||
}
|
||||
|
||||
// Render load indicator:
|
||||
if (
|
||||
(player.playerState == .loading || player.playerState == .buffering)
|
||||
&& self.controller.player.playWhenReady // Avoid showing indicator before user has pressed play
|
||||
) {
|
||||
self.loadIndicator.startAnimating()
|
||||
} else {
|
||||
self.loadIndicator.stopAnimating()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AudioPlayer Event Handlers
|
||||
|
||||
func handleAudioPlayerStateChange(data: AudioPlayer.StateChangeEventData) {
|
||||
print("state=\(data)")
|
||||
DispatchQueue.main.async {
|
||||
self.render()
|
||||
}
|
||||
}
|
||||
|
||||
func handlePlayWhenReadyChange(data: AudioPlayer.PlayWhenReadyChangeData) {
|
||||
print("playWhenReady=\(data)")
|
||||
DispatchQueue.main.async {
|
||||
self.render()
|
||||
}
|
||||
}
|
||||
|
||||
func handleAudioPlayerPlaybackEnd(data: AudioPlayer.PlaybackEndEventData) {
|
||||
print("playEndReason=\(data)")
|
||||
}
|
||||
|
||||
func handleAudioPlayerSecondElapsed(data: AudioPlayer.SecondElapseEventData) {
|
||||
if !isScrubbing {
|
||||
DispatchQueue.main.async {
|
||||
self.renderTimeValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleAudioPlayerDidSeek(data: AudioPlayer.SeekEventData) {
|
||||
isScrubbing = false
|
||||
}
|
||||
|
||||
func handleAudioPlayerUpdateDuration(data: AudioPlayer.UpdateDurationEventData) {
|
||||
DispatchQueue.main.async {
|
||||
self.renderTimeValues()
|
||||
}
|
||||
}
|
||||
|
||||
func handleAVPlayerRecreated() {
|
||||
try? controller.audioSessionController.set(category: .playback)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "SwiftAudioEx",
|
||||
platforms: [.iOS(.v11)],
|
||||
platforms: [.iOS(.v11), .macOS(.v11)],
|
||||
products: [
|
||||
.library(
|
||||
name: "SwiftAudioEx",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# SwiftAudioEx
|
||||
|
||||
[](https://codecov.io/gh/doublesymmetry/SwiftAudioEx)
|
||||
[](http://cocoapods.org/pods/SwiftAudioEx)
|
||||
[](http://cocoapods.org/pods/SwiftAudioEx)
|
||||
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
public typealias AudioItemImage = UIImage
|
||||
#elseif os(macOS)
|
||||
import AppKit
|
||||
public typealias AudioItemImage = NSImage
|
||||
#endif
|
||||
|
||||
public enum SourceType {
|
||||
case stream
|
||||
@@ -15,19 +22,17 @@ public enum SourceType {
|
||||
}
|
||||
|
||||
public protocol AudioItem {
|
||||
|
||||
func getSourceUrl() -> String
|
||||
func getArtist() -> String?
|
||||
func getTitle() -> String?
|
||||
func getAlbumTitle() -> String?
|
||||
func getSourceType() -> SourceType
|
||||
func getArtwork(_ handler: @escaping (UIImage?) -> Void)
|
||||
func getArtwork(_ handler: @escaping (AudioItemImage?) -> Void)
|
||||
|
||||
}
|
||||
|
||||
/// Make your `AudioItem`-subclass conform to this protocol to control which AVAudioTimePitchAlgorithm is used for each item.
|
||||
public protocol TimePitching {
|
||||
|
||||
func getPitchAlgorithmType() -> AVAudioTimePitchAlgorithm
|
||||
|
||||
}
|
||||
@@ -42,8 +47,8 @@ public protocol AssetOptionsProviding {
|
||||
func getAssetOptions() -> [String: Any]
|
||||
}
|
||||
|
||||
public class DefaultAudioItem: AudioItem {
|
||||
|
||||
public class DefaultAudioItem: AudioItem, Identifiable {
|
||||
|
||||
public var audioUrl: String
|
||||
|
||||
public var artist: String?
|
||||
@@ -54,9 +59,9 @@ public class DefaultAudioItem: AudioItem {
|
||||
|
||||
public var sourceType: SourceType
|
||||
|
||||
public var artwork: UIImage?
|
||||
public var artwork: AudioItemImage?
|
||||
|
||||
public init(audioUrl: String, artist: String? = nil, title: String? = nil, albumTitle: String? = nil, sourceType: SourceType, artwork: UIImage? = nil) {
|
||||
public init(audioUrl: String, artist: String? = nil, title: String? = nil, albumTitle: String? = nil, sourceType: SourceType, artwork: AudioItemImage? = nil) {
|
||||
self.audioUrl = audioUrl
|
||||
self.artist = artist
|
||||
self.title = title
|
||||
@@ -85,7 +90,7 @@ public class DefaultAudioItem: AudioItem {
|
||||
sourceType
|
||||
}
|
||||
|
||||
public func getArtwork(_ handler: @escaping (UIImage?) -> Void) {
|
||||
public func getArtwork(_ handler: @escaping (AudioItemImage?) -> Void) {
|
||||
handler(artwork)
|
||||
}
|
||||
|
||||
@@ -96,12 +101,12 @@ public class DefaultAudioItemTimePitching: DefaultAudioItem, TimePitching {
|
||||
|
||||
public var pitchAlgorithmType: AVAudioTimePitchAlgorithm
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: AudioItemImage?) {
|
||||
pitchAlgorithmType = AVAudioTimePitchAlgorithm.timeDomain
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?, audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm) {
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: AudioItemImage?, audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm) {
|
||||
pitchAlgorithmType = audioTimePitchAlgorithm
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
@@ -116,12 +121,12 @@ public class DefaultAudioItemInitialTime: DefaultAudioItem, InitialTiming {
|
||||
|
||||
public var initialTime: TimeInterval
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: AudioItemImage?) {
|
||||
initialTime = 0.0
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?, initialTime: TimeInterval) {
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: AudioItemImage?, initialTime: TimeInterval) {
|
||||
self.initialTime = initialTime
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
@@ -137,12 +142,12 @@ public class DefaultAudioItemAssetOptionsProviding: DefaultAudioItem, AssetOptio
|
||||
|
||||
public var options: [String: Any]
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: AudioItemImage?) {
|
||||
options = [:]
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?, options: [String: Any]) {
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: AudioItemImage?, options: [String: Any]) {
|
||||
self.options = options
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
try action()
|
||||
|
||||
if playWhenReady == true {
|
||||
if playWhenReady == true, playbackError == nil {
|
||||
self.playWhenReady = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
|
||||
#if os(iOS)
|
||||
protocol AudioSession {
|
||||
|
||||
var isOtherAudioPlaying: Bool { get }
|
||||
@@ -30,3 +30,4 @@ protocol AudioSession {
|
||||
}
|
||||
|
||||
extension AVAudioSession: AudioSession {}
|
||||
#endif
|
||||
|
||||
@@ -13,6 +13,8 @@ public enum InterruptionType: Equatable {
|
||||
case ended(shouldResume: Bool)
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
public protocol AudioSessionControllerDelegate: AnyObject {
|
||||
func handleInterruption(type: InterruptionType)
|
||||
}
|
||||
@@ -129,3 +131,5 @@ public class AudioSessionController {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,7 +13,7 @@ protocol QueueManagerDelegate: AnyObject {
|
||||
func onSkippedToSameCurrentItem()
|
||||
}
|
||||
|
||||
class QueueManager<T> {
|
||||
class QueueManager<Element> {
|
||||
|
||||
fileprivate let recursiveLock = NSRecursiveLock()
|
||||
|
||||
@@ -54,7 +54,7 @@ class QueueManager<T> {
|
||||
/**
|
||||
All items held by the queue.
|
||||
*/
|
||||
private(set) var items: [T] = [] {
|
||||
private(set) var items: [Element] = [] {
|
||||
didSet {
|
||||
return synchronize {
|
||||
if oldValue.count == 0 && items.count > 0 {
|
||||
@@ -64,7 +64,7 @@ class QueueManager<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public var nextItems: [T] {
|
||||
public var nextItems: [Element] {
|
||||
return synchronize {
|
||||
return currentIndex == -1 || currentIndex == items.count - 1
|
||||
? []
|
||||
@@ -72,7 +72,7 @@ class QueueManager<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public var previousItems: [T] {
|
||||
public var previousItems: [Element] {
|
||||
return synchronize {
|
||||
return currentIndex <= 0
|
||||
? []
|
||||
@@ -83,7 +83,7 @@ class QueueManager<T> {
|
||||
/**
|
||||
The current item for the queue.
|
||||
*/
|
||||
public var current: T? {
|
||||
public var current: Element? {
|
||||
return synchronize {
|
||||
return 0 <= _currentIndex && _currentIndex < items.count ? items[_currentIndex] : nil
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class QueueManager<T> {
|
||||
|
||||
- parameter item: The `AudioItem` to be added.
|
||||
*/
|
||||
public func add(_ item: T) {
|
||||
public func add(_ item: Element) {
|
||||
synchronize {
|
||||
items.append(item)
|
||||
}
|
||||
@@ -125,7 +125,7 @@ class QueueManager<T> {
|
||||
|
||||
- parameter items: The `AudioItem`s to be added.
|
||||
*/
|
||||
public func add(_ items: [T]) {
|
||||
public func add(_ items: [Element]) {
|
||||
synchronize {
|
||||
if (items.count == 0) { return }
|
||||
self.items.append(contentsOf: items)
|
||||
@@ -138,14 +138,14 @@ class QueueManager<T> {
|
||||
- parameter items: The `AudioItem`s to be added.
|
||||
- parameter at: The index to insert the items at.
|
||||
*/
|
||||
public func add(_ items: [T], at index: Int) throws {
|
||||
public func add(_ items: [Element], at index: Int) throws {
|
||||
try synchronizeThrows {
|
||||
if (items.count == 0) { return }
|
||||
guard index >= 0 && self.items.count >= index else {
|
||||
throw AudioPlayerError.QueueError.invalidIndex(index: index, message: "Index to insert at has to be non-negative and equal to or smaller than the number of items: (\(items.count))")
|
||||
}
|
||||
// Correct index when items were inserted in front of it:
|
||||
if (self.items.count > 1 && currentIndex >= index) {
|
||||
if (self.items.count > 0 && currentIndex >= index) {
|
||||
currentIndex += items.count
|
||||
}
|
||||
self.items.insert(contentsOf: items, at: index)
|
||||
@@ -157,7 +157,7 @@ class QueueManager<T> {
|
||||
case previous = -1
|
||||
}
|
||||
|
||||
private func skip(direction: SkipDirection, wrap: Bool) -> T? {
|
||||
private func skip(direction: SkipDirection, wrap: Bool) -> Element? {
|
||||
let count = items.count
|
||||
if (current == nil || count == 0) {
|
||||
return nil
|
||||
@@ -174,9 +174,7 @@ class QueueManager<T> {
|
||||
let oldIndex = currentIndex
|
||||
currentIndex = max(0, min(items.count - 1, index))
|
||||
if (oldIndex != currentIndex) {
|
||||
defer {
|
||||
delegate?.onCurrentItemChanged()
|
||||
}
|
||||
delegate?.onCurrentItemChanged()
|
||||
}
|
||||
}
|
||||
return current
|
||||
@@ -188,7 +186,7 @@ class QueueManager<T> {
|
||||
- returns: The next (or current) item.
|
||||
*/
|
||||
@discardableResult
|
||||
public func next(wrap: Bool = false) -> T? {
|
||||
public func next(wrap: Bool = false) -> Element? {
|
||||
synchronize {
|
||||
return skip(direction: SkipDirection.next, wrap: wrap);
|
||||
}
|
||||
@@ -201,7 +199,7 @@ class QueueManager<T> {
|
||||
- returns: The previous item.
|
||||
*/
|
||||
@discardableResult
|
||||
public func previous(wrap: Bool = false) -> T? {
|
||||
public func previous(wrap: Bool = false) -> Element? {
|
||||
return synchronize {
|
||||
return skip(direction: SkipDirection.previous, wrap: wrap);
|
||||
}
|
||||
@@ -216,7 +214,7 @@ class QueueManager<T> {
|
||||
- returns: The item at the index.
|
||||
*/
|
||||
@discardableResult
|
||||
public func jump(to index: Int) throws -> T {
|
||||
public func jump(to index: Int) throws -> Element {
|
||||
var skippedToSameCurrentItem = false
|
||||
var currentItemChanged = false
|
||||
let result = try synchronizeThrows {
|
||||
@@ -268,7 +266,8 @@ class QueueManager<T> {
|
||||
- throws: AudioPlayerError.QueueError
|
||||
- returns: The removed item.
|
||||
*/
|
||||
public func removeItem(at index: Int) throws -> T {
|
||||
@discardableResult
|
||||
public func removeItem(at index: Int) throws -> Element {
|
||||
var currentItemChanged = false
|
||||
let result = try synchronizeThrows {
|
||||
try throwIfQueueEmpty()
|
||||
@@ -294,7 +293,7 @@ class QueueManager<T> {
|
||||
|
||||
- parameter item: The item to set as the new current item.
|
||||
*/
|
||||
public func replaceCurrentItem(with item: T) {
|
||||
public func replaceCurrentItem(with item: Element) {
|
||||
var currentItemChanged = false
|
||||
synchronize {
|
||||
if currentIndex == -1 {
|
||||
|
||||
@@ -41,7 +41,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperStateWhenPlayingSourceShouldBePlaying() {
|
||||
@@ -52,7 +52,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperStateWhenPausingSourceShouldBePaused() {
|
||||
@@ -68,7 +68,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperStateWhenTogglingFromPlayShouldBePaused() {
|
||||
@@ -84,7 +84,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperStateWhenStoppingShouldBeStopped() {
|
||||
@@ -100,7 +100,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperStateLoadingWithInitialTimeShouldBePlaying() {
|
||||
@@ -114,7 +114,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: LongSource.url, playWhenReady: true, initialTime: 4.0)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Duration tests
|
||||
@@ -131,7 +131,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Current time tests
|
||||
@@ -152,7 +152,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
expectation.fulfill()
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperSeekingShouldSeekWhileNotYetLoaded() {
|
||||
@@ -163,7 +163,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wrapper.seek(to: seekTime)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperSeekByShouldSeek() {
|
||||
@@ -176,7 +176,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
expectation.fulfill()
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperLoadingSourceWithInitialTimeShouldSeek() {
|
||||
@@ -185,7 +185,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
expectation.fulfill()
|
||||
}
|
||||
wrapper.load(from: LongSource.url, playWhenReady: false, initialTime: 4.0)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Rate tests
|
||||
@@ -202,7 +202,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperTimeObserverWhenUpdatedShouldUpdateTheObserversPeriodicObserverTimeInterval() {
|
||||
|
||||
@@ -23,7 +23,7 @@ class AudioPlayerEventTests: XCTestCase {
|
||||
func testEventAddListener() {
|
||||
let listener = EventListener()
|
||||
event.addListener(listener, listener.handleEvent)
|
||||
waitTrue(self.event.invokers.count > 0, timeout: 5)
|
||||
waitTrue(self.event.invokers.count > 0, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testEventRemoveListener() {
|
||||
@@ -32,7 +32,7 @@ class AudioPlayerEventTests: XCTestCase {
|
||||
listener = nil
|
||||
event.emit(data: ())
|
||||
|
||||
waitEqual(self.event.invokers.count, 0, timeout: 5)
|
||||
waitEqual(self.event.invokers.count, 0, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testEventAddMultipleListeners() {
|
||||
@@ -44,7 +44,7 @@ class AudioPlayerEventTests: XCTestCase {
|
||||
return listener
|
||||
}
|
||||
|
||||
waitEqual(self.event.invokers.count, listeners.count, timeout: 5)
|
||||
waitEqual(self.event.invokers.count, listeners.count, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testEventRemoveOneListener() {
|
||||
@@ -59,6 +59,6 @@ class AudioPlayerEventTests: XCTestCase {
|
||||
let listenerToRemove = listeners[listeners.count / 2]
|
||||
event.removeListener(listenerToRemove)
|
||||
|
||||
waitEqual(self.event.invokers.count, listeners.count - 1, timeout: 5)
|
||||
waitEqual(self.event.invokers.count, listeners.count - 1, timeout: defaultTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
XCTAssertFalse(audioPlayer.playWhenReady)
|
||||
audioPlayer.load(item: FiveSecondSourceWithInitialTimeOfFourSeconds.getAudioItem())
|
||||
|
||||
wait(for: [expectation], timeout: 5)
|
||||
wait(for: [expectation], timeout: defaultTimeout)
|
||||
|
||||
XCTAssertTrue(seekCompleted)
|
||||
XCTAssertTrue(audioPlayer.currentTime >= 4)
|
||||
@@ -72,7 +72,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
|
||||
func testSetDurationAfterLoading() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testOnUpdateDurationReceivedAfterLoading() {
|
||||
@@ -87,7 +87,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
|
||||
wait(for: [expectation], timeout: 5) // Adjust the timeout as needed
|
||||
wait(for: [expectation], timeout: defaultTimeout) // Adjust the timeout as needed
|
||||
|
||||
XCTAssertTrue(receivedUpdateDuration)
|
||||
}
|
||||
@@ -95,17 +95,17 @@ class AudioPlayerTests: XCTestCase {
|
||||
func testResetDurationAfterLoadingAgain() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testResetDurationAfterReset() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: defaultTimeout)
|
||||
audioPlayer.clear()
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
}
|
||||
@@ -130,7 +130,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
)
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
|
||||
wait(for: [expectation], timeout: 5) // Adjust the timeout as needed
|
||||
wait(for: [expectation], timeout: defaultTimeout) // Adjust the timeout as needed
|
||||
|
||||
XCTAssertNotNil(audioPlayer.playbackError)
|
||||
XCTAssertEqual(audioPlayer.playerState, .failed)
|
||||
@@ -168,10 +168,10 @@ class AudioPlayerTests: XCTestCase {
|
||||
)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.play()
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testRetryLoadingAfterFailureWithPlayWhenReady() {
|
||||
@@ -185,10 +185,10 @@ class AudioPlayerTests: XCTestCase {
|
||||
)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.playWhenReady = true
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testRetryLoadingAfterFailureWithReload() {
|
||||
@@ -202,10 +202,10 @@ class AudioPlayerTests: XCTestCase {
|
||||
)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.reload(startFromCurrentTime: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testLoadResourceSucceedsAfterPreviousFailure() {
|
||||
@@ -218,13 +218,13 @@ class AudioPlayerTests: XCTestCase {
|
||||
let failItem = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream)
|
||||
|
||||
audioPlayer.load(item: failItem, playWhenReady: false)
|
||||
waitTrue(didReceiveFail, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .failed, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .failed], timeout: 5)
|
||||
waitTrue(didReceiveFail, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .failed, timeout: defaultTimeout)
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .failed], timeout: defaultTimeout)
|
||||
|
||||
self.audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitTrue(self.audioPlayer.playbackError == nil, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .playing], timeout: 5)
|
||||
waitTrue(self.audioPlayer.playbackError == nil, timeout: defaultTimeout)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .idle, .loading, .playing], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testLoadResourceSucceedsAfterPreviousFailureWithPlayWhenReady() {
|
||||
@@ -237,11 +237,11 @@ class AudioPlayerTests: XCTestCase {
|
||||
let item = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitTrue(didReceiveFail, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .failed, timeout: 5)
|
||||
waitTrue(didReceiveFail, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .failed, timeout: defaultTimeout)
|
||||
|
||||
self.audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitTrue(self.audioPlayer.playbackError == nil, timeout: 5)
|
||||
waitTrue(self.audioPlayer.playbackError == nil, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - States
|
||||
@@ -257,115 +257,115 @@ class AudioPlayerTests: XCTestCase {
|
||||
|
||||
func testReadyStateAfterLoadSource() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testPlayingStateAfterLoadSourceWithPlayWhenReady() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testReliableOrderOfEvents() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.pause()
|
||||
expectedEvents.append(.paused)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.play()
|
||||
expectedEvents.append(.playing)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.clear()
|
||||
expectedEvents.append(.idle)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testUpdatePlayWhenReadyAfterExternalPause() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: defaultTimeout)
|
||||
|
||||
// Simulate AVPlayer becoming paused due to external reason:
|
||||
audioPlayer.wrapper.rate = 0
|
||||
expectedEvents.append(.paused)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
XCTAssertFalse(self.audioPlayer.playWhenReady)
|
||||
}
|
||||
|
||||
func testReliableOrderOfEventsAtEndCallStop() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.pause()
|
||||
expectedEvents.append(.paused)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
expectedEvents.append(.playing)
|
||||
audioPlayer.play()
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.stop()
|
||||
expectedEvents.append(.stopped)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testReliableOrderOfEventsAfterLoadingAfterReset() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.clear()
|
||||
expectedEvents.append(.idle)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
expectedEvents.append(contentsOf: [.loading, .playing])
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testPlayingStateAfterPlay() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.play()
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testPausedStateAfterPause() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.pause()
|
||||
waitEqual(self.audioPlayer.playerState, .paused, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .paused, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testPausedStateAfterSettingPlayWhenReadyToFalse() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.playWhenReady = false
|
||||
waitEqual(self.audioPlayer.playerState, .paused, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .paused, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testPlayingStateAfterSettingPlayWhenReadyToTrue() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.playWhenReady = true
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testStoppedStateAfterStop() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - State (Current Time)
|
||||
@@ -383,7 +383,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
}
|
||||
|
||||
audioPlayer.load(item: LongSource.getAudioItem(), playWhenReady: true)
|
||||
waitTrue(onSecondsElapseTime > 0, timeout: 5)
|
||||
waitTrue(onSecondsElapseTime > 0, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Buffer
|
||||
@@ -411,22 +411,22 @@ class AudioPlayerTests: XCTestCase {
|
||||
|
||||
func testSeekingBeforeLoadingComplete() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
XCTAssertTrue(audioPlayer.playerState == .loading)
|
||||
XCTAssertTrue(audioPlayer.playerState == .buffering)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.75, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.75, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testSeekingAfterLoadingComplete() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.75, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.75, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testSeekingWhenPaused() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: false)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitEqual(self.audioPlayer.currentTime, 4.75, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 4.75, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testSeekingWhenStopped() {
|
||||
@@ -435,7 +435,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
waitForSeek(audioPlayer, to: 2)
|
||||
audioPlayer.stop()
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Rate
|
||||
@@ -466,7 +466,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
audioPlayer.rate = 10
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: defaultTimeout)
|
||||
|
||||
if let start = start, let end = end {
|
||||
let duration = end.timeIntervalSince(start)
|
||||
@@ -498,7 +498,7 @@ class AudioPlayerTests: XCTestCase {
|
||||
}
|
||||
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: defaultTimeout)
|
||||
|
||||
if let start = start, let end = end {
|
||||
let duration = end.timeIntervalSince(start)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import XCTest
|
||||
import AVFoundation
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AudioSessionControllerTests: XCTestCase {
|
||||
@@ -89,3 +92,5 @@ class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelega
|
||||
self.interruptionType = type
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
|
||||
@@ -64,3 +66,5 @@ class FailingAudioSession: AudioSession {
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -75,16 +75,15 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
|
||||
XCTAssertNil(audioPlayer.currentItem)
|
||||
XCTAssertEqual(audioPlayer.playerState, AudioPlayerState.idle)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .idle], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .idle], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testLoadAfterRemoval() {
|
||||
testRemovingItemAfterAdding()
|
||||
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
XCTAssertNotEqual(audioPlayer.currentItem?.getSourceUrl(), FiveSecondSource.getAudioItem().getSourceUrl())
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .idle, .loading, .playing], timeout: 5)
|
||||
XCTAssertEqual(audioPlayer.playerState, AudioPlayerState.playing)
|
||||
waitTrue(self.playerStateEventListener.statesWithoutBuffering.contains(.playing), timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testAddingMultipleItems() {
|
||||
@@ -100,7 +99,16 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
XCTAssertEqual(audioPlayer.items.count, 1)
|
||||
XCTAssertEqual(audioPlayer.currentItem?.getSourceUrl(), ShortSource.getAudioItem().getSourceUrl())
|
||||
}
|
||||
|
||||
|
||||
// Covers: https://github.com/doublesymmetry/SwiftAudioEx/pull/81
|
||||
func testAddingItemWhenOnlyOneTrackInQueue() throws {
|
||||
audioPlayer.add(item: FiveSecondSource.getAudioItem())
|
||||
audioPlayer.play()
|
||||
try audioPlayer.add(items: [ShortSource.getAudioItem()], at: 0)
|
||||
XCTAssertEqual(audioPlayer.items.count, 2)
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 1)
|
||||
}
|
||||
|
||||
// MARK: - Next Items
|
||||
|
||||
func testNextItemsEmptyOnCreate() {
|
||||
@@ -166,25 +174,24 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
|
||||
// Test next
|
||||
audioPlayer.next()
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .paused, .loading, .paused], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .paused, .loading, .paused], timeout: defaultTimeout)
|
||||
XCTAssertEqual(audioPlayer.previousItems.count, 1)
|
||||
waitEqual(self.playbackEndEventListener.lastReason, .skippedToNext, timeout: 5)
|
||||
waitEqual(self.playbackEndEventListener.lastReason, .skippedToNext, timeout: defaultTimeout)
|
||||
|
||||
// Test stop
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: 5)
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: defaultTimeout)
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: defaultTimeout)
|
||||
|
||||
// Test stop again
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: 5)
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: defaultTimeout)
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: defaultTimeout)
|
||||
|
||||
// Test previous
|
||||
audioPlayer.previous()
|
||||
waitEqual(self.audioPlayer.playerState, .loading, timeout: 5)
|
||||
// should not have emitted playbackEnd .skippedToPrevious because playback was already stopped previously
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: 5)
|
||||
XCTAssertEqual(self.audioPlayer.playerState, .loading)
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: defaultTimeout)
|
||||
|
||||
}
|
||||
|
||||
@@ -218,14 +225,14 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.pause()
|
||||
|
||||
// It should have gone into .paused state from .loading and then into .ready because playback can be started
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .paused, .ready], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .paused, .ready], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Stop
|
||||
|
||||
func testStopOnEmptyQueue() {
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.playerStateEventListener.states, [.stopped], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.states, [.stopped], timeout: defaultTimeout)
|
||||
|
||||
// It should not have emitted a playbackEnd event
|
||||
XCTAssertNil(playbackEndEventListener.lastReason)
|
||||
@@ -239,10 +246,10 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.stop()
|
||||
|
||||
// It should have emitted a playbackEnd .playerStopped event
|
||||
waitEqual(self.playbackEndEventListener.lastReason, .playerStopped, timeout: 5)
|
||||
waitEqual(self.playbackEndEventListener.lastReason, .playerStopped, timeout: defaultTimeout)
|
||||
|
||||
// It should have mutated player state from .loading to .stopped
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .stopped], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .stopped], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Load
|
||||
@@ -253,7 +260,7 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
XCTAssertNotNil(audioPlayer.currentItem)
|
||||
|
||||
// It should have started loading, but not playing yet
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .paused, .ready], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .paused, .ready], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testLoadItemAfterPlaying() {
|
||||
@@ -262,12 +269,12 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
XCTAssertNotNil(audioPlayer.currentItem)
|
||||
|
||||
// It should have started playing
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .playing], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .playing], timeout: defaultTimeout)
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
|
||||
XCTAssertEqual(audioPlayer.items.count, 1)
|
||||
XCTAssertEqual(audioPlayer.currentItem?.getSourceUrl(), Source.getAudioItem().getSourceUrl())
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering.prefix(4), [.loading, .playing, .loading, .playing], timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering.prefix(4), [.loading, .playing, .loading, .playing], timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Next
|
||||
@@ -282,10 +289,10 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
// should go to previous item and not play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenPausedWithoutPlaying() {
|
||||
@@ -293,10 +300,10 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.pause()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
// should go to previous item and not play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenPlaying() {
|
||||
@@ -304,10 +311,10 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
// should go to previous item and play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Previous
|
||||
@@ -323,11 +330,11 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.next()
|
||||
audioPlayer.previous()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.previousItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.previousItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
// should go to previous item and play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testPreviousWhenPaused() {
|
||||
@@ -336,11 +343,11 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.pause()
|
||||
audioPlayer.previous()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.previousItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.previousItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
// should go to previous item and not play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Move
|
||||
@@ -354,7 +361,7 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.repeatMode = RepeatMode.off
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ended, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ended, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testMoveItemsRepeatModeQueue() {
|
||||
@@ -366,9 +373,9 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.repeatMode = RepeatMode.queue
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testMoveItemsRepeatModeTrack() {
|
||||
@@ -380,10 +387,10 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.repeatMode = RepeatMode.track
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitTrue(self.audioPlayer.currentTime < 4.6, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime < 4.6, timeout: defaultTimeout)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: defaultTimeout)
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 1)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Off - Two Items)
|
||||
@@ -398,16 +405,16 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
setupRepeatModeOffTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: defaultTimeout)
|
||||
|
||||
// Allow final track to end
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
waitEqual(self.audioPlayer.currentTime, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.index, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 5, accuracy: 0.1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.index, 1, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeOff() {
|
||||
@@ -415,16 +422,16 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
audioPlayer.play()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: defaultTimeout)
|
||||
|
||||
// Calling next on the final track
|
||||
audioPlayer.next()
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentTime, 5, accuracy: 0.1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Track - Two Items)
|
||||
@@ -439,19 +446,19 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
setupRepeatModeTrackTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeTrack() {
|
||||
setupRepeatModeTrackTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
|
||||
@@ -467,28 +474,28 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
setupRepeatModeQueueTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: defaultTimeout)
|
||||
|
||||
// Allow the final track to end
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 1, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeQueue() {
|
||||
setupRepeatModeQueueTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextTwiceWhenRepeatModeQueue() {
|
||||
@@ -498,13 +505,13 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
|
||||
audioPlayer.next()
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 1)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: defaultTimeout)
|
||||
|
||||
audioPlayer.next()
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 0)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Off - One Item)
|
||||
@@ -518,15 +525,15 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
setupRepeatModeOffOneItemTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeOffOneItem() {
|
||||
setupRepeatModeOffOneItemTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
// TODO: Test this more thoroughly?
|
||||
}
|
||||
|
||||
@@ -541,19 +548,19 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
setupRepeatModeTrackOneItemTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, nil, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, nil, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeTrackOneItem() {
|
||||
setupRepeatModeTrackOneItemTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Queue - One Item)
|
||||
@@ -567,11 +574,11 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
setupRepeatModeQueueOneItemTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.5, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime < 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.5, timeout: defaultTimeout)
|
||||
waitTrue(self.audioPlayer.currentTime < 1, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeQueueOneItem() {
|
||||
@@ -579,10 +586,10 @@ class QueuedAudioPlayerTests: XCTestCase {
|
||||
waitForSeek(audioPlayer, to: 2)
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime < 1.9, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
waitTrue(self.audioPlayer.currentTime < 1.9, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: defaultTimeout)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: defaultTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,68 +4,85 @@ import XCTest
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
extension XCTestCase {
|
||||
var defaultTimeout: TimeInterval {
|
||||
if ProcessInfo.processInfo.environment["CI"] != nil {
|
||||
return 20
|
||||
} else {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
||||
func waitForSeek(_ audioPlayer: AudioPlayer, to time: Double) {
|
||||
let seekEventListener = QueuedAudioPlayer.SeekEventListener()
|
||||
audioPlayer.event.seek.addListener(seekEventListener, seekEventListener.handleEvent)
|
||||
audioPlayer.seek(to: time)
|
||||
|
||||
waitEqual(seekEventListener.eventResult.0, time, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(seekEventListener.eventResult.1, true, timeout: 5)
|
||||
}
|
||||
|
||||
func waitTrue(_ expression: @autoclosure @escaping () -> Bool, timeout: TimeInterval) {
|
||||
let expectation = XCTestExpectation(description: "Value should eventually equal expected value")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
while !expression() {
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
waitEqual(seekEventListener.eventResult.0, time, accuracy: 0.1, timeout: defaultTimeout)
|
||||
waitEqual(seekEventListener.eventResult.1, true, timeout: defaultTimeout)
|
||||
}
|
||||
|
||||
func waitEqual<T: Equatable>(_ expression1: @autoclosure @escaping () -> T, _ expression2: @autoclosure @escaping () -> T, timeout: TimeInterval) {
|
||||
let expectation = XCTestExpectation(description: "Value should eventually equal expected value")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
while expression1() != expression2() {
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||
if expression1() == expression2() {
|
||||
expectation.fulfill()
|
||||
timer.invalidate()
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
RunLoop.current.add(timer, forMode: .default)
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
|
||||
timer.invalidate()
|
||||
}
|
||||
|
||||
func waitEqual<T: Equatable>(_ expression1: @autoclosure @escaping () -> T, _ expression2: @autoclosure @escaping () -> T, accuracy: T, timeout: TimeInterval) where T: FloatingPoint {
|
||||
let expectation = XCTestExpectation(description: "Value should eventually equal expected value with accuracy")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
let startTime = Date()
|
||||
while abs(expression1() - expression2()) > accuracy {
|
||||
if Date().timeIntervalSince(startTime) >= timeout {
|
||||
break
|
||||
}
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||
if abs(expression1() - expression2()) < accuracy {
|
||||
expectation.fulfill()
|
||||
timer.invalidate()
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
return wait(for: [expectation], timeout: timeout)
|
||||
|
||||
RunLoop.current.add(timer, forMode: .default)
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
|
||||
timer.invalidate()
|
||||
}
|
||||
|
||||
func waitEqual<T1: Equatable, T2: Equatable>(_ expression1: @autoclosure @escaping () -> (T1, T2), _ expression2: @autoclosure @escaping () -> (T1, T2), timeout: TimeInterval) {
|
||||
let expectation = XCTestExpectation(description: "Values should eventually be equal")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
while expression1() != expression2() {
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||
if expression1() == expression2() {
|
||||
expectation.fulfill()
|
||||
timer.invalidate()
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
|
||||
RunLoop.current.add(timer, forMode: .default)
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
|
||||
timer.invalidate()
|
||||
}
|
||||
|
||||
|
||||
func waitTrue(_ expression: @autoclosure @escaping () -> Bool, timeout: TimeInterval) {
|
||||
let expectation = XCTestExpectation(description: "Expression should eventually be true")
|
||||
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||
if expression() {
|
||||
expectation.fulfill()
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
RunLoop.current.add(timer, forMode: .default)
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import Foundation
|
||||
import SwiftAudioEx
|
||||
import UIKit
|
||||
|
||||
struct Source {
|
||||
static let path: String = Bundle.module.path(forResource: "TestSound", ofType: "m4a")!
|
||||
static let url: URL = URL(fileURLWithPath: Source.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
return DefaultAudioItem(audioUrl: self.path, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .file, artwork: UIImage())
|
||||
return DefaultAudioItem(audioUrl: self.path, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .file, artwork: AudioItemImage())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user