switch to official cast sdk.
This commit is contained in:
parent
b5571639aa
commit
dc5ec29bd8
|
@ -25,15 +25,6 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Cache Carthage dependencies
|
|
||||||
id: cache-vlckit
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: Carthage
|
|
||||||
key: ${{ runner.os }}-${{ matrix.scheme }}-carthage-${{ hashFiles('**/Cartfile.resolved') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.scheme }}-carthage-
|
|
||||||
|
|
||||||
- name: Cache Swift packages
|
- name: Cache Swift packages
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
|
@ -48,16 +39,6 @@ jobs:
|
||||||
path: "~/Library/Developer/Xcode/DerivedData"
|
path: "~/Library/Developer/Xcode/DerivedData"
|
||||||
key: ${{ runner.os }}-${{ matrix.scheme }}-deriveddata
|
key: ${{ runner.os }}-${{ matrix.scheme }}-deriveddata
|
||||||
|
|
||||||
- name: Install Ruby (for Carthage)
|
|
||||||
if: steps.cache-vlckit.outputs.cache-hit != 'true'
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: 2.6 # Not needed with a .ruby-version file
|
|
||||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
||||||
- name: Update Carthage dependencies
|
|
||||||
if: steps.cache-vlckit.outputs.cache-hit != 'true'
|
|
||||||
run: "carthage update"
|
|
||||||
|
|
||||||
- name: xcodebuild!
|
- name: xcodebuild!
|
||||||
run: |
|
run: |
|
||||||
xcodebuild build -project "JellyfinPlayer.xcodeproj" \
|
xcodebuild build -project "JellyfinPlayer.xcodeproj" \
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
build/
|
build/
|
||||||
DerivedData/
|
DerivedData/
|
||||||
|
|
||||||
|
Pods/
|
||||||
|
Podfile.lock
|
||||||
dynatraceSymbols.zip
|
dynatraceSymbols.zip
|
||||||
Cartfile.resolved
|
Cartfile.resolved
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
@ -93,4 +95,4 @@ iOSInjectionProject/
|
||||||
.Spotlight-V100
|
.Spotlight-V100
|
||||||
.Trashes
|
.Trashes
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
2
Cartfile
2
Cartfile
|
@ -1,2 +0,0 @@
|
||||||
binary "https://code.videolan.org/videolan/VLCKit/raw/master/Packaging/MobileVLCKit.json" ~> 3.3.0
|
|
||||||
binary "https://code.videolan.org/videolan/VLCKit/raw/master/Packaging/TVVLCKit.json" ~> 3.3.0
|
|
|
@ -41,35 +41,6 @@
|
||||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; };
|
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; };
|
||||||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; };
|
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; };
|
||||||
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VideoPlayer.swift */; };
|
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VideoPlayer.swift */; };
|
||||||
5362E4D3267D461F000E2F71 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 5362E4D2267D461F000E2F71 /* SwiftProtobuf */; };
|
|
||||||
5362E4D6267D4671000E2F71 /* Result in Frameworks */ = {isa = PBXBuildFile; productRef = 5362E4D5267D4671000E2F71 /* Result */; };
|
|
||||||
5362E4D9267D4695000E2F71 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 5362E4D8267D4695000E2F71 /* SwiftyJSON */; };
|
|
||||||
5362E500267D4707000E2F71 /* CastClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4DE267D4707000E2F71 /* CastClient.swift */; };
|
|
||||||
5362E501267D4707000E2F71 /* CastDeviceScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4DF267D4707000E2F71 /* CastDeviceScanner.swift */; };
|
|
||||||
5362E502267D4707000E2F71 /* ReceiverControlChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E1267D4707000E2F71 /* ReceiverControlChannel.swift */; };
|
|
||||||
5362E503267D4707000E2F71 /* Channelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E2267D4707000E2F71 /* Channelable.swift */; };
|
|
||||||
5362E504267D4707000E2F71 /* CastChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E3267D4707000E2F71 /* CastChannel.swift */; };
|
|
||||||
5362E505267D4707000E2F71 /* DeviceAuthChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E4267D4707000E2F71 /* DeviceAuthChannel.swift */; };
|
|
||||||
5362E506267D4707000E2F71 /* MediaControlChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E5267D4707000E2F71 /* MediaControlChannel.swift */; };
|
|
||||||
5362E507267D4707000E2F71 /* HeartbeatChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E6267D4707000E2F71 /* HeartbeatChannel.swift */; };
|
|
||||||
5362E508267D4707000E2F71 /* DeviceSetupChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E7267D4707000E2F71 /* DeviceSetupChannel.swift */; };
|
|
||||||
5362E509267D4707000E2F71 /* DeviceConnectionChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E8267D4707000E2F71 /* DeviceConnectionChannel.swift */; };
|
|
||||||
5362E50A267D4707000E2F71 /* RequestSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4E9267D4707000E2F71 /* RequestSink.swift */; };
|
|
||||||
5362E50B267D4707000E2F71 /* MultizoneControlChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4EA267D4707000E2F71 /* MultizoneControlChannel.swift */; };
|
|
||||||
5362E50C267D4707000E2F71 /* DeviceDiscoveryChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4EB267D4707000E2F71 /* DeviceDiscoveryChannel.swift */; };
|
|
||||||
5362E50D267D4707000E2F71 /* CastDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4ED267D4707000E2F71 /* CastDevice.swift */; };
|
|
||||||
5362E50E267D4707000E2F71 /* CastStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4EE267D4707000E2F71 /* CastStatus.swift */; };
|
|
||||||
5362E50F267D4707000E2F71 /* CastApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4EF267D4707000E2F71 /* CastApp.swift */; };
|
|
||||||
5362E510267D4707000E2F71 /* CastMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4F0267D4707000E2F71 /* CastMessage.swift */; };
|
|
||||||
5362E511267D4707000E2F71 /* CastMediaStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4F1267D4707000E2F71 /* CastMediaStatus.swift */; };
|
|
||||||
5362E512267D4707000E2F71 /* CastMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4F2267D4707000E2F71 /* CastMedia.swift */; };
|
|
||||||
5362E513267D4707000E2F71 /* CastMultizoneDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4F3267D4707000E2F71 /* CastMultizoneDevice.swift */; };
|
|
||||||
5362E514267D4707000E2F71 /* CastMultizoneStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4F4267D4707000E2F71 /* CastMultizoneStatus.swift */; };
|
|
||||||
5362E515267D4707000E2F71 /* AppAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4F5267D4707000E2F71 /* AppAvailability.swift */; };
|
|
||||||
5362E517267D4707000E2F71 /* CASTV2Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4FA267D4707000E2F71 /* CASTV2Protocol.swift */; };
|
|
||||||
5362E518267D4707000E2F71 /* cast_channel.proto in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4FD267D4707000E2F71 /* cast_channel.proto */; };
|
|
||||||
5362E519267D4707000E2F71 /* cast_channel.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4FE267D4707000E2F71 /* cast_channel.pb.swift */; };
|
|
||||||
5362E51A267D4707000E2F71 /* CastV2PlatformReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5362E4FF267D4707000E2F71 /* CastV2PlatformReader.swift */; };
|
|
||||||
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
||||||
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
||||||
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D73267BA8170004248C /* BackgroundManager.swift */; };
|
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D73267BA8170004248C /* BackgroundManager.swift */; };
|
||||||
|
@ -111,6 +82,9 @@
|
||||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DE4BD1267098F300739748 /* SearchBarView.swift */; };
|
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DE4BD1267098F300739748 /* SearchBarView.swift */; };
|
||||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
|
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
|
||||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */; };
|
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */; };
|
||||||
|
53EC6E1E267E80AC006DD26A /* Pods_JellyfinPlayer_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */; };
|
||||||
|
53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */; };
|
||||||
|
53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 53EC6E24267EB10F006DD26A /* SwiftyJSON */; };
|
||||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; };
|
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; };
|
||||||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */; };
|
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */; };
|
||||||
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; };
|
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; };
|
||||||
|
@ -128,8 +102,6 @@
|
||||||
625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; };
|
625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; };
|
||||||
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */; };
|
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */; };
|
||||||
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 625CB5792678C4A400530A6E /* ActivityIndicator */; };
|
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 625CB5792678C4A400530A6E /* ActivityIndicator */; };
|
||||||
625CB57E2678E81E00530A6E /* TVVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */; };
|
|
||||||
625CB57F2678E81E00530A6E /* TVVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
|
||||||
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
||||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
|
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
|
||||||
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
|
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
|
||||||
|
@ -165,8 +137,6 @@
|
||||||
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; };
|
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; };
|
||||||
62E632F3267D54030063E547 /* DetailItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* DetailItemViewModel.swift */; };
|
62E632F3267D54030063E547 /* DetailItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* DetailItemViewModel.swift */; };
|
||||||
62E632F4267D54030063E547 /* DetailItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* DetailItemViewModel.swift */; };
|
62E632F4267D54030063E547 /* DetailItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* DetailItemViewModel.swift */; };
|
||||||
62EC3527267665D8000E9F2D /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; };
|
|
||||||
62EC3528267665D8000E9F2D /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
|
||||||
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
|
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
|
||||||
62EC352D26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
|
62EC352D26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
|
||||||
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
||||||
|
@ -197,17 +167,6 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
625CB5802678E81E00530A6E /* Embed Frameworks */ = {
|
|
||||||
isa = PBXCopyFilesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
dstPath = "";
|
|
||||||
dstSubfolderSpec = 10;
|
|
||||||
files = (
|
|
||||||
625CB57F2678E81E00530A6E /* TVVLCKit.xcframework in Embed Frameworks */,
|
|
||||||
);
|
|
||||||
name = "Embed Frameworks";
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
628B95312670CABE0091AF3B /* Embed App Extensions */ = {
|
628B95312670CABE0091AF3B /* Embed App Extensions */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -219,20 +178,11 @@
|
||||||
name = "Embed App Extensions";
|
name = "Embed App Extensions";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
62EC3529267665D8000E9F2D /* Embed Frameworks */ = {
|
|
||||||
isa = PBXCopyFilesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
dstPath = "";
|
|
||||||
dstSubfolderSpec = 10;
|
|
||||||
files = (
|
|
||||||
62EC3528267665D8000E9F2D /* MobileVLCKit.xcframework in Embed Frameworks */,
|
|
||||||
);
|
|
||||||
name = "Embed Frameworks";
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
531690E4267ABD5C005D8AB9 /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; };
|
531690E4267ABD5C005D8AB9 /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; };
|
||||||
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||||
531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; };
|
531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -272,33 +222,6 @@
|
||||||
5362E4C4267D40F0000E2F71 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
5362E4C4267D40F0000E2F71 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||||
5362E4C6267D40F4000E2F71 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
|
5362E4C6267D40F4000E2F71 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
|
||||||
5362E4C8267D40F7000E2F71 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
5362E4C8267D40F7000E2F71 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||||
5362E4DE267D4707000E2F71 /* CastClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastClient.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4DF267D4707000E2F71 /* CastDeviceScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastDeviceScanner.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E1267D4707000E2F71 /* ReceiverControlChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiverControlChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E2267D4707000E2F71 /* Channelable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Channelable.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E3267D4707000E2F71 /* CastChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E4267D4707000E2F71 /* DeviceAuthChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceAuthChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E5267D4707000E2F71 /* MediaControlChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaControlChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E6267D4707000E2F71 /* HeartbeatChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartbeatChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E7267D4707000E2F71 /* DeviceSetupChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSetupChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E8267D4707000E2F71 /* DeviceConnectionChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceConnectionChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4E9267D4707000E2F71 /* RequestSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestSink.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4EA267D4707000E2F71 /* MultizoneControlChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultizoneControlChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4EB267D4707000E2F71 /* DeviceDiscoveryChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceDiscoveryChannel.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4ED267D4707000E2F71 /* CastDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastDevice.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4EE267D4707000E2F71 /* CastStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastStatus.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4EF267D4707000E2F71 /* CastApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastApp.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4F0267D4707000E2F71 /* CastMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastMessage.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4F1267D4707000E2F71 /* CastMediaStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastMediaStatus.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4F2267D4707000E2F71 /* CastMedia.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastMedia.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4F3267D4707000E2F71 /* CastMultizoneDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastMultizoneDevice.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4F4267D4707000E2F71 /* CastMultizoneStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastMultizoneStatus.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4F5267D4707000E2F71 /* AppAvailability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAvailability.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4F7267D4707000E2F71 /* ChromeCastCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChromeCastCore.h; sourceTree = "<group>"; };
|
|
||||||
5362E4FA267D4707000E2F71 /* CASTV2Protocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CASTV2Protocol.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4FD267D4707000E2F71 /* cast_channel.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; path = cast_channel.proto; sourceTree = "<group>"; };
|
|
||||||
5362E4FE267D4707000E2F71 /* cast_channel.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = cast_channel.pb.swift; sourceTree = "<group>"; };
|
|
||||||
5362E4FF267D4707000E2F71 /* CastV2PlatformReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CastV2PlatformReader.swift; sourceTree = "<group>"; };
|
|
||||||
5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; };
|
5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; };
|
||||||
536D3D73267BA8170004248C /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
|
536D3D73267BA8170004248C /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
|
||||||
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = "<group>"; };
|
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -326,7 +249,6 @@
|
||||||
53ABFDDD267974E300886593 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
|
53ABFDDD267974E300886593 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
|
||||||
53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
|
53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
|
||||||
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = JellyfinPlayer.entitlements; sourceTree = "<group>"; };
|
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = JellyfinPlayer.entitlements; sourceTree = "<group>"; };
|
||||||
53D5E3DA264B460200BADDC8 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; };
|
|
||||||
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
|
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
|
||||||
53DE4BD1267098F300739748 /* SearchBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarView.swift; sourceTree = "<group>"; };
|
53DE4BD1267098F300739748 /* SearchBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarView.swift; sourceTree = "<group>"; };
|
||||||
53DF641D263D9C0600A7CD1A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
|
53DF641D263D9C0600A7CD1A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -371,6 +293,10 @@
|
||||||
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
|
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
|
||||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
||||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||||
|
BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -378,9 +304,9 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
53EC6E1E267E80AC006DD26A /* Pods_JellyfinPlayer_tvOS.framework in Frameworks */,
|
||||||
53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */,
|
53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */,
|
||||||
535870912669D7A800D05A09 /* Introspect in Frameworks */,
|
535870912669D7A800D05A09 /* Introspect in Frameworks */,
|
||||||
625CB57E2678E81E00530A6E /* TVVLCKit.xcframework in Frameworks */,
|
|
||||||
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
|
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
|
||||||
536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
|
536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
|
||||||
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
|
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
|
||||||
|
@ -393,15 +319,13 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
5362E4D9267D4695000E2F71 /* SwiftyJSON in Frameworks */,
|
|
||||||
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */,
|
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */,
|
||||||
|
53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */,
|
||||||
|
53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */,
|
||||||
53352571265EA0A0006CCA86 /* Introspect in Frameworks */,
|
53352571265EA0A0006CCA86 /* Introspect in Frameworks */,
|
||||||
621C638026672A30004216EA /* NukeUI in Frameworks */,
|
621C638026672A30004216EA /* NukeUI in Frameworks */,
|
||||||
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */,
|
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */,
|
||||||
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */,
|
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */,
|
||||||
5362E4D6267D4671000E2F71 /* Result in Frameworks */,
|
|
||||||
5362E4D3267D461F000E2F71 /* SwiftProtobuf in Frameworks */,
|
|
||||||
62EC3527267665D8000E9F2D /* MobileVLCKit.xcframework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -495,96 +419,6 @@
|
||||||
path = Typings;
|
path = Typings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
5362E4DC267D4707000E2F71 /* OpenCastSwift */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4DD267D4707000E2F71 /* Networking */,
|
|
||||||
5362E4EC267D4707000E2F71 /* Models */,
|
|
||||||
5362E4F6267D4707000E2F71 /* Supporting Files */,
|
|
||||||
5362E4F9267D4707000E2F71 /* Definitions */,
|
|
||||||
5362E4FB267D4707000E2F71 /* Helpers */,
|
|
||||||
);
|
|
||||||
path = OpenCastSwift;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5362E4DD267D4707000E2F71 /* Networking */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4DE267D4707000E2F71 /* CastClient.swift */,
|
|
||||||
5362E4DF267D4707000E2F71 /* CastDeviceScanner.swift */,
|
|
||||||
5362E4E0267D4707000E2F71 /* Channels */,
|
|
||||||
);
|
|
||||||
path = Networking;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5362E4E0267D4707000E2F71 /* Channels */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4E1267D4707000E2F71 /* ReceiverControlChannel.swift */,
|
|
||||||
5362E4E2267D4707000E2F71 /* Channelable.swift */,
|
|
||||||
5362E4E3267D4707000E2F71 /* CastChannel.swift */,
|
|
||||||
5362E4E4267D4707000E2F71 /* DeviceAuthChannel.swift */,
|
|
||||||
5362E4E5267D4707000E2F71 /* MediaControlChannel.swift */,
|
|
||||||
5362E4E6267D4707000E2F71 /* HeartbeatChannel.swift */,
|
|
||||||
5362E4E7267D4707000E2F71 /* DeviceSetupChannel.swift */,
|
|
||||||
5362E4E8267D4707000E2F71 /* DeviceConnectionChannel.swift */,
|
|
||||||
5362E4E9267D4707000E2F71 /* RequestSink.swift */,
|
|
||||||
5362E4EA267D4707000E2F71 /* MultizoneControlChannel.swift */,
|
|
||||||
5362E4EB267D4707000E2F71 /* DeviceDiscoveryChannel.swift */,
|
|
||||||
);
|
|
||||||
path = Channels;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5362E4EC267D4707000E2F71 /* Models */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4ED267D4707000E2F71 /* CastDevice.swift */,
|
|
||||||
5362E4EE267D4707000E2F71 /* CastStatus.swift */,
|
|
||||||
5362E4EF267D4707000E2F71 /* CastApp.swift */,
|
|
||||||
5362E4F0267D4707000E2F71 /* CastMessage.swift */,
|
|
||||||
5362E4F1267D4707000E2F71 /* CastMediaStatus.swift */,
|
|
||||||
5362E4F2267D4707000E2F71 /* CastMedia.swift */,
|
|
||||||
5362E4F3267D4707000E2F71 /* CastMultizoneDevice.swift */,
|
|
||||||
5362E4F4267D4707000E2F71 /* CastMultizoneStatus.swift */,
|
|
||||||
5362E4F5267D4707000E2F71 /* AppAvailability.swift */,
|
|
||||||
);
|
|
||||||
path = Models;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5362E4F6267D4707000E2F71 /* Supporting Files */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4F7267D4707000E2F71 /* ChromeCastCore.h */,
|
|
||||||
);
|
|
||||||
path = "Supporting Files";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5362E4F9267D4707000E2F71 /* Definitions */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4FA267D4707000E2F71 /* CASTV2Protocol.swift */,
|
|
||||||
);
|
|
||||||
path = Definitions;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5362E4FB267D4707000E2F71 /* Helpers */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4FC267D4707000E2F71 /* Proto */,
|
|
||||||
5362E4FF267D4707000E2F71 /* CastV2PlatformReader.swift */,
|
|
||||||
);
|
|
||||||
path = Helpers;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
5362E4FC267D4707000E2F71 /* Proto */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
5362E4FD267D4707000E2F71 /* cast_channel.proto */,
|
|
||||||
5362E4FE267D4707000E2F71 /* cast_channel.pb.swift */,
|
|
||||||
);
|
|
||||||
path = Proto;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
536D3D77267BB9650004248C /* Components */ = {
|
536D3D77267BB9650004248C /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -598,13 +432,13 @@
|
||||||
5377CBE8263B596A003A4E83 = {
|
5377CBE8263B596A003A4E83 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
53D5E3DA264B460200BADDC8 /* Cartfile */,
|
|
||||||
53D5E3DB264B47EE00BADDC8 /* Frameworks */,
|
53D5E3DB264B47EE00BADDC8 /* Frameworks */,
|
||||||
5377CBF3263B596A003A4E83 /* JellyfinPlayer */,
|
5377CBF3263B596A003A4E83 /* JellyfinPlayer */,
|
||||||
535870612669D21600D05A09 /* JellyfinPlayer tvOS */,
|
535870612669D21600D05A09 /* JellyfinPlayer tvOS */,
|
||||||
5377CBF2263B596A003A4E83 /* Products */,
|
5377CBF2263B596A003A4E83 /* Products */,
|
||||||
535870752669D60C00D05A09 /* Shared */,
|
535870752669D60C00D05A09 /* Shared */,
|
||||||
628B95252670CABD0091AF3B /* WidgetExtension */,
|
628B95252670CABD0091AF3B /* WidgetExtension */,
|
||||||
|
C78797A232E2B8774099D1E9 /* Pods */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
@ -621,7 +455,6 @@
|
||||||
5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = {
|
5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5362E4DC267D4707000E2F71 /* OpenCastSwift */,
|
|
||||||
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */,
|
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */,
|
||||||
5377CBF8263B596B003A4E83 /* Assets.xcassets */,
|
5377CBF8263B596B003A4E83 /* Assets.xcassets */,
|
||||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
||||||
|
@ -689,6 +522,8 @@
|
||||||
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */,
|
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */,
|
||||||
628B95212670CABD0091AF3B /* WidgetKit.framework */,
|
628B95212670CABD0091AF3B /* WidgetKit.framework */,
|
||||||
628B95232670CABD0091AF3B /* SwiftUI.framework */,
|
628B95232670CABD0091AF3B /* SwiftUI.framework */,
|
||||||
|
3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */,
|
||||||
|
EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -741,6 +576,17 @@
|
||||||
path = Resources;
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
C78797A232E2B8774099D1E9 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */,
|
||||||
|
BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */,
|
||||||
|
DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */,
|
||||||
|
D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
@ -748,10 +594,11 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 535870712669D21700D05A09 /* Build configuration list for PBXNativeTarget "JellyfinPlayer tvOS" */;
|
buildConfigurationList = 535870712669D21700D05A09 /* Build configuration list for PBXNativeTarget "JellyfinPlayer tvOS" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
E7370E1AA68C6CB254E46F2C /* [CP] Check Pods Manifest.lock */,
|
||||||
5358705C2669D21600D05A09 /* Sources */,
|
5358705C2669D21600D05A09 /* Sources */,
|
||||||
5358705D2669D21600D05A09 /* Frameworks */,
|
5358705D2669D21600D05A09 /* Frameworks */,
|
||||||
5358705E2669D21600D05A09 /* Resources */,
|
5358705E2669D21600D05A09 /* Resources */,
|
||||||
625CB5802678E81E00530A6E /* Embed Frameworks */,
|
6AB6F1DD2C8AD942F71C8A32 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -774,12 +621,14 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer iOS" */;
|
buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer iOS" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
6435C3C2E610FE34AD537AC1 /* [CP] Check Pods Manifest.lock */,
|
||||||
5377CBED263B596A003A4E83 /* Sources */,
|
5377CBED263B596A003A4E83 /* Sources */,
|
||||||
5377CBEE263B596A003A4E83 /* Frameworks */,
|
5377CBEE263B596A003A4E83 /* Frameworks */,
|
||||||
5377CBEF263B596A003A4E83 /* Resources */,
|
5377CBEF263B596A003A4E83 /* Resources */,
|
||||||
5302F8322658B74800647A2E /* CopyFiles */,
|
5302F8322658B74800647A2E /* CopyFiles */,
|
||||||
628B95312670CABE0091AF3B /* Embed App Extensions */,
|
628B95312670CABE0091AF3B /* Embed App Extensions */,
|
||||||
62EC3529267665D8000E9F2D /* Embed Frameworks */,
|
E8DDF21F62DFCE8CE76666BA /* [CP] Embed Pods Frameworks */,
|
||||||
|
83FD120CA10FD0E91DAD83C9 /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -793,9 +642,7 @@
|
||||||
621C637F26672A30004216EA /* NukeUI */,
|
621C637F26672A30004216EA /* NukeUI */,
|
||||||
53A431BC266B0FF20016769F /* JellyfinAPI */,
|
53A431BC266B0FF20016769F /* JellyfinAPI */,
|
||||||
625CB5792678C4A400530A6E /* ActivityIndicator */,
|
625CB5792678C4A400530A6E /* ActivityIndicator */,
|
||||||
5362E4D2267D461F000E2F71 /* SwiftProtobuf */,
|
53EC6E24267EB10F006DD26A /* SwiftyJSON */,
|
||||||
5362E4D5267D4671000E2F71 /* Result */,
|
|
||||||
5362E4D8267D4695000E2F71 /* SwiftyJSON */,
|
|
||||||
);
|
);
|
||||||
productName = JellyfinPlayer;
|
productName = JellyfinPlayer;
|
||||||
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
|
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
|
||||||
|
@ -863,9 +710,7 @@
|
||||||
53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
|
53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
|
||||||
625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */,
|
625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */,
|
||||||
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */,
|
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */,
|
||||||
5362E4D1267D461F000E2F71 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
53EC6E23267EB10F006DD26A /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
|
||||||
5362E4D4267D4671000E2F71 /* XCRemoteSwiftPackageReference "Result" */,
|
|
||||||
5362E4D7267D4695000E2F71 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
|
|
||||||
);
|
);
|
||||||
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -910,6 +755,104 @@
|
||||||
};
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
6435C3C2E610FE34AD537AC1 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-JellyfinPlayer iOS-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
6AB6F1DD2C8AD942F71C8A32 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
83FD120CA10FD0E91DAD83C9 /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
E7370E1AA68C6CB254E46F2C /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-JellyfinPlayer tvOS-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
E8DDF21F62DFCE8CE76666BA /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
5358705C2669D21600D05A09 /* Sources */ = {
|
5358705C2669D21600D05A09 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
|
@ -967,80 +910,54 @@
|
||||||
files = (
|
files = (
|
||||||
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
|
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
|
||||||
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
|
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
|
||||||
5362E507267D4707000E2F71 /* HeartbeatChannel.swift in Sources */,
|
|
||||||
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
|
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
|
||||||
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
|
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
|
||||||
5362E506267D4707000E2F71 /* MediaControlChannel.swift in Sources */,
|
|
||||||
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
|
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
|
||||||
62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
|
62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
|
||||||
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
|
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
|
||||||
5362E500267D4707000E2F71 /* CastClient.swift in Sources */,
|
|
||||||
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
||||||
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
|
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
|
||||||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
|
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
|
||||||
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
||||||
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
|
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
|
||||||
5362E508267D4707000E2F71 /* DeviceSetupChannel.swift in Sources */,
|
|
||||||
53DE4BD02670961400739748 /* EpisodeItemView.swift in Sources */,
|
53DE4BD02670961400739748 /* EpisodeItemView.swift in Sources */,
|
||||||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
||||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
||||||
5362E50E267D4707000E2F71 /* CastStatus.swift in Sources */,
|
|
||||||
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
|
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
|
||||||
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
|
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
|
||||||
625CB56F2678C23300530A6E /* HomeView.swift in Sources */,
|
625CB56F2678C23300530A6E /* HomeView.swift in Sources */,
|
||||||
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
||||||
5362E513267D4707000E2F71 /* CastMultizoneDevice.swift in Sources */,
|
|
||||||
625CB5682678B6FB00530A6E /* SplashView.swift in Sources */,
|
625CB5682678B6FB00530A6E /* SplashView.swift in Sources */,
|
||||||
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */,
|
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */,
|
||||||
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
||||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
||||||
532E68CF267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift in Sources */,
|
532E68CF267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift in Sources */,
|
||||||
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */,
|
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */,
|
||||||
5362E50A267D4707000E2F71 /* RequestSink.swift in Sources */,
|
|
||||||
5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */,
|
5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */,
|
||||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
|
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
|
||||||
5362E514267D4707000E2F71 /* CastMultizoneStatus.swift in Sources */,
|
|
||||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
||||||
5362E503267D4707000E2F71 /* Channelable.swift in Sources */,
|
|
||||||
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */,
|
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */,
|
||||||
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
||||||
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
|
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
|
||||||
62E632F3267D54030063E547 /* DetailItemViewModel.swift in Sources */,
|
62E632F3267D54030063E547 /* DetailItemViewModel.swift in Sources */,
|
||||||
5362E50C267D4707000E2F71 /* DeviceDiscoveryChannel.swift in Sources */,
|
|
||||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
||||||
5362E50D267D4707000E2F71 /* CastDevice.swift in Sources */,
|
|
||||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
||||||
5362E509267D4707000E2F71 /* DeviceConnectionChannel.swift in Sources */,
|
|
||||||
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
||||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
||||||
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
|
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
|
||||||
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
||||||
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
|
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
|
||||||
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
|
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
|
||||||
5362E512267D4707000E2F71 /* CastMedia.swift in Sources */,
|
|
||||||
5362E519267D4707000E2F71 /* cast_channel.pb.swift in Sources */,
|
|
||||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
||||||
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
|
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
|
||||||
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
|
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
|
||||||
5362E50B267D4707000E2F71 /* MultizoneControlChannel.swift in Sources */,
|
|
||||||
5362E517267D4707000E2F71 /* CASTV2Protocol.swift in Sources */,
|
|
||||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
|
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
|
||||||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
||||||
5362E518267D4707000E2F71 /* cast_channel.proto in Sources */,
|
|
||||||
625CB56C2678C0FD00530A6E /* MainTabView.swift in Sources */,
|
625CB56C2678C0FD00530A6E /* MainTabView.swift in Sources */,
|
||||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||||
5362E511267D4707000E2F71 /* CastMediaStatus.swift in Sources */,
|
|
||||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||||
5362E505267D4707000E2F71 /* DeviceAuthChannel.swift in Sources */,
|
|
||||||
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */,
|
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */,
|
||||||
5362E50F267D4707000E2F71 /* CastApp.swift in Sources */,
|
|
||||||
5362E501267D4707000E2F71 /* CastDeviceScanner.swift in Sources */,
|
|
||||||
5362E502267D4707000E2F71 /* ReceiverControlChannel.swift in Sources */,
|
|
||||||
5362E504267D4707000E2F71 /* CastChannel.swift in Sources */,
|
|
||||||
5362E515267D4707000E2F71 /* AppAvailability.swift in Sources */,
|
|
||||||
5362E51A267D4707000E2F71 /* CastV2PlatformReader.swift in Sources */,
|
|
||||||
5362E510267D4707000E2F71 /* CastMessage.swift in Sources */,
|
|
||||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
||||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */,
|
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */,
|
||||||
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */,
|
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */,
|
||||||
|
@ -1079,6 +996,7 @@
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
535870722669D21700D05A09 /* Debug */ = {
|
535870722669D21700D05A09 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
@ -1088,10 +1006,7 @@
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/Carthage/Build/TVVLCKit.xcframework/tvos-arm64",
|
|
||||||
);
|
|
||||||
INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist";
|
INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -1109,6 +1024,7 @@
|
||||||
};
|
};
|
||||||
535870732669D21700D05A09 /* Release */ = {
|
535870732669D21700D05A09 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
@ -1118,10 +1034,7 @@
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/Carthage/Build/TVVLCKit.xcframework/tvos-arm64",
|
|
||||||
);
|
|
||||||
INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist";
|
INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -1258,6 +1171,7 @@
|
||||||
};
|
};
|
||||||
5377CC1C263B596B003A4E83 /* Debug */ = {
|
5377CC1C263B596B003A4E83 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
@ -1271,10 +1185,7 @@
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
EXCLUDED_ARCHS = "";
|
EXCLUDED_ARCHS = "";
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/Carthage/Build/MobileVLCKit.xcframework/ios-arm64_armv7_armv7s",
|
|
||||||
);
|
|
||||||
INFOPLIST_FILE = JellyfinPlayer/Info.plist;
|
INFOPLIST_FILE = JellyfinPlayer/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -1294,6 +1205,7 @@
|
||||||
};
|
};
|
||||||
5377CC1D263B596B003A4E83 /* Release */ = {
|
5377CC1D263B596B003A4E83 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
@ -1308,10 +1220,7 @@
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
EXCLUDED_ARCHS = "";
|
EXCLUDED_ARCHS = "";
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/Carthage/Build/MobileVLCKit.xcframework/ios-arm64_armv7_armv7s",
|
|
||||||
);
|
|
||||||
INFOPLIST_FILE = JellyfinPlayer/Info.plist;
|
INFOPLIST_FILE = JellyfinPlayer/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -1437,30 +1346,6 @@
|
||||||
minimumVersion = 19.0.0;
|
minimumVersion = 19.0.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
5362E4D1267D461F000E2F71 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/apple/swift-protobuf.git";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 1.0.0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
5362E4D4267D4671000E2F71 /* XCRemoteSwiftPackageReference "Result" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/antitypical/Result";
|
|
||||||
requirement = {
|
|
||||||
branch = master;
|
|
||||||
kind = branch;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
5362E4D7267D4695000E2F71 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
|
|
||||||
requirement = {
|
|
||||||
branch = master;
|
|
||||||
kind = branch;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */ = {
|
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/PGSSoft/ParallaxView";
|
repositoryURL = "https://github.com/PGSSoft/ParallaxView";
|
||||||
|
@ -1477,6 +1362,14 @@
|
||||||
kind = branch;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
53EC6E23267EB10F006DD26A /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
|
||||||
|
requirement = {
|
||||||
|
branch = master;
|
||||||
|
kind = branch;
|
||||||
|
};
|
||||||
|
};
|
||||||
621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */ = {
|
621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/kean/NukeUI";
|
repositoryURL = "https://github.com/kean/NukeUI";
|
||||||
|
@ -1521,21 +1414,6 @@
|
||||||
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||||
productName = NukeUI;
|
productName = NukeUI;
|
||||||
};
|
};
|
||||||
5362E4D2267D461F000E2F71 /* SwiftProtobuf */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 5362E4D1267D461F000E2F71 /* XCRemoteSwiftPackageReference "swift-protobuf" */;
|
|
||||||
productName = SwiftProtobuf;
|
|
||||||
};
|
|
||||||
5362E4D5267D4671000E2F71 /* Result */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 5362E4D4267D4671000E2F71 /* XCRemoteSwiftPackageReference "Result" */;
|
|
||||||
productName = Result;
|
|
||||||
};
|
|
||||||
5362E4D8267D4695000E2F71 /* SwiftyJSON */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 5362E4D7267D4695000E2F71 /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
|
|
||||||
productName = SwiftyJSON;
|
|
||||||
};
|
|
||||||
536D3D7C267BD5F90004248C /* ActivityIndicator */ = {
|
536D3D7C267BD5F90004248C /* ActivityIndicator */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */;
|
package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */;
|
||||||
|
@ -1561,6 +1439,11 @@
|
||||||
package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */;
|
package = 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */;
|
||||||
productName = ActivityIndicator;
|
productName = ActivityIndicator;
|
||||||
};
|
};
|
||||||
|
53EC6E24267EB10F006DD26A /* SwiftyJSON */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 53EC6E23267EB10F006DD26A /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
|
||||||
|
productName = SwiftyJSON;
|
||||||
|
};
|
||||||
621C637F26672A30004216EA /* NukeUI */ = {
|
621C637F26672A30004216EA /* NukeUI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||||
|
|
|
@ -73,15 +73,6 @@
|
||||||
"version": "3.1.2"
|
"version": "3.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"package": "Result",
|
|
||||||
"repositoryURL": "https://github.com/antitypical/Result",
|
|
||||||
"state": {
|
|
||||||
"branch": "master",
|
|
||||||
"revision": "c30700bfcab7f555bf1d386fdd609407a94f369c",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "swift-protobuf",
|
"package": "swift-protobuf",
|
||||||
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1250"
|
LastUpgradeVersion = "1300"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1250"
|
LastUpgradeVersion = "1300"
|
||||||
wasCreatedForAppExtension = "YES"
|
wasCreatedForAppExtension = "YES"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:JellyfinPlayer.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?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>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"package": "ActivityIndicator",
|
||||||
|
"repositoryURL": "https://github.com/duyquang91/ActivityIndicator",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "0101a02196f6a67cf26f6434b007d3db6bd07fee",
|
||||||
|
"version": "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "AnyCodable",
|
||||||
|
"repositoryURL": "https://github.com/Flight-School/AnyCodable",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "876d162385e9862ae8b3c8d65dc301312b040005",
|
||||||
|
"version": "0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Gifu",
|
||||||
|
"repositoryURL": "https://github.com/kaishin/Gifu",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "51f2eab32903e336f590c013267cfa4d7f8b06c4",
|
||||||
|
"version": "3.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "jellyfin-sdk-swift",
|
||||||
|
"repositoryURL": "https://github.com/jellyfin/jellyfin-sdk-swift",
|
||||||
|
"state": {
|
||||||
|
"branch": "main",
|
||||||
|
"revision": "5cdc2419f547b3f31dc96f5eccaf3f303f44184b",
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "keychain-swift",
|
||||||
|
"repositoryURL": "https://github.com/evgenyneu/keychain-swift",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "96fb84f45a96630e7583903bd7e08cf095c7a7ef",
|
||||||
|
"version": "19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Nuke",
|
||||||
|
"repositoryURL": "https://github.com/kean/Nuke.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "69ae6d5b8c4b898450432f94bd35f863d3830cfc",
|
||||||
|
"version": "10.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "NukeUI",
|
||||||
|
"repositoryURL": "https://github.com/kean/NukeUI",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "d2580b8d22b29c6244418d8e4b568f3162191460",
|
||||||
|
"version": "0.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "ParallaxView",
|
||||||
|
"repositoryURL": "https://github.com/PGSSoft/ParallaxView",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "a4165b0edd9c9c923a1d6e3e4c9a807302a1a475",
|
||||||
|
"version": "3.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftUI-Introspect",
|
||||||
|
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2",
|
||||||
|
"version": "0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftyJSON",
|
||||||
|
"repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON",
|
||||||
|
"state": {
|
||||||
|
"branch": "master",
|
||||||
|
"revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
|
@ -64,7 +64,7 @@ struct EpisodeItemView: View {
|
||||||
self.playbackInfo.shouldShowPlayer = true
|
self.playbackInfo.shouldShowPlayer = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(viewModel.item.getItemProgressString() == "" ? "Play" : "\(viewModel.item.getItemProgressString()) left")
|
Text(viewModel.item.getItemProgressString() == "" ? "Play" : viewModel.item.getItemProgressString())
|
||||||
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
||||||
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||||
|
<string>${PRODUCT_NAME} uses Bluetooth to discover nearby Cast devices.</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoadsForMedia</key>
|
<key>NSAllowsArbitraryLoadsForMedia</key>
|
||||||
|
|
|
@ -44,9 +44,6 @@ struct LatestMediaView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if viewModel.isLoading {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 190)
|
.frame(height: 190)
|
||||||
|
|
|
@ -70,7 +70,7 @@ struct MovieItemView: View {
|
||||||
self.playbackInfo.shouldShowPlayer = true
|
self.playbackInfo.shouldShowPlayer = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(viewModel.item.getItemProgressString() == "" ? "Play" : "\(viewModel.item.getItemProgressString()) left")
|
Text(viewModel.item.getItemProgressString() == "" ? "Play" : viewModel.item.getItemProgressString())
|
||||||
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
||||||
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
//
|
|
||||||
// CASTV2Protocol.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct CastNamespace {
|
|
||||||
static let auth = "urn:x-cast:com.google.cast.tp.deviceauth"
|
|
||||||
static let connection = "urn:x-cast:com.google.cast.tp.connection"
|
|
||||||
static let heartbeat = "urn:x-cast:com.google.cast.tp.heartbeat"
|
|
||||||
static let receiver = "urn:x-cast:com.google.cast.receiver"
|
|
||||||
static let media = "urn:x-cast:com.google.cast.media"
|
|
||||||
static let discovery = "urn:x-cast:com.google.cast.receiver.discovery"
|
|
||||||
static let setup = "urn:x-cast:com.google.cast.setup"
|
|
||||||
static let multizone = "urn:x-cast:com.google.cast.multizone"
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CastMessageType: String {
|
|
||||||
case ping = "PING"
|
|
||||||
case pong = "PONG"
|
|
||||||
case connect = "CONNECT"
|
|
||||||
case close = "CLOSE"
|
|
||||||
case status = "RECEIVER_STATUS"
|
|
||||||
case launch = "LAUNCH"
|
|
||||||
case stop = "STOP"
|
|
||||||
case load = "LOAD"
|
|
||||||
case pause = "PAUSE"
|
|
||||||
case play = "PLAY"
|
|
||||||
case seek = "SEEK"
|
|
||||||
case setVolume = "SET_VOLUME"
|
|
||||||
case setDeviceVolume = "SET_DEVICE_VOLUME"
|
|
||||||
case statusRequest = "GET_STATUS"
|
|
||||||
case availableApps = "GET_APP_AVAILABILITY"
|
|
||||||
case mediaStatus = "MEDIA_STATUS"
|
|
||||||
case getDeviceInfo = "GET_DEVICE_INFO"
|
|
||||||
case deviceInfo = "DEVICE_INFO"
|
|
||||||
case getDeviceConfig = "eureka_info"
|
|
||||||
case setDeviceConfig = "set_eureka_info"
|
|
||||||
case getAppDeviceId = "get_app_device_id"
|
|
||||||
case multizoneStatus = "MULTIZONE_STATUS"
|
|
||||||
case deviceAdded = "DEVICE_ADDED"
|
|
||||||
case deviceUpdated = "DEVICE_UPDATED"
|
|
||||||
case deviceRemoved = "DEVICE_REMOVED"
|
|
||||||
case invalidRequest = "INVALID_REQUEST"
|
|
||||||
case mdxSessionStatus = "mdxSessionStatus"
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CastJSONPayloadKeys {
|
|
||||||
static let type = "type"
|
|
||||||
static let requestId = "requestId"
|
|
||||||
static let status = "status"
|
|
||||||
static let applications = "applications"
|
|
||||||
static let appId = "appId"
|
|
||||||
static let displayName = "displayName"
|
|
||||||
static let sessionId = "sessionId"
|
|
||||||
static let transportId = "transportId"
|
|
||||||
static let statusText = "statusText"
|
|
||||||
static let isIdleScreen = "isIdleScreen"
|
|
||||||
static let namespaces = "namespaces"
|
|
||||||
static let volume = "volume"
|
|
||||||
static let controlType = "controlType"
|
|
||||||
static let level = "level"
|
|
||||||
static let muted = "muted"
|
|
||||||
static let mediaSessionId = "mediaSessionId"
|
|
||||||
static let availability = "availability"
|
|
||||||
static let name = "name"
|
|
||||||
static let currentTime = "currentTime"
|
|
||||||
static let media = "media"
|
|
||||||
static let repeatMode = "repeatMode"
|
|
||||||
static let autoplay = "autoplay"
|
|
||||||
static let contentId = "contentId"
|
|
||||||
static let contentType = "contentType"
|
|
||||||
static let streamType = "streamType"
|
|
||||||
static let metadata = "metadata"
|
|
||||||
static let metadataType = "metadataType"
|
|
||||||
static let title = "title"
|
|
||||||
static let images = "images"
|
|
||||||
static let url = "url"
|
|
||||||
static let activeTrackIds = "activeTrackIds"
|
|
||||||
static let playbackRate = "playbackRate"
|
|
||||||
static let playerState = "playerState"
|
|
||||||
static let deviceId = "deviceId"
|
|
||||||
static let device = "device"
|
|
||||||
static let devices = "devices"
|
|
||||||
static let capabilities = "capabilities"
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CastConstants {
|
|
||||||
static let sender = "sender-0"
|
|
||||||
static let receiver = "receiver-0"
|
|
||||||
static let transport = "transport-0"
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
//
|
|
||||||
// CastV2PlatformReader.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
let maxBufferLength = 8192
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class CastV2PlatformReader {
|
|
||||||
let stream: InputStream
|
|
||||||
var readPosition = 0
|
|
||||||
var buffer = Data(capacity: maxBufferLength)
|
|
||||||
|
|
||||||
init(stream: InputStream) {
|
|
||||||
self.stream = stream
|
|
||||||
}
|
|
||||||
|
|
||||||
func readStream() {
|
|
||||||
objc_sync_enter(self)
|
|
||||||
defer { objc_sync_exit(self) }
|
|
||||||
|
|
||||||
var totalBytesRead = 0
|
|
||||||
let bufferSize = 32
|
|
||||||
|
|
||||||
while stream.hasBytesAvailable {
|
|
||||||
var bytes = [UInt8](repeating: 0, count: bufferSize)
|
|
||||||
|
|
||||||
let bytesRead = stream.read(&bytes, maxLength: bufferSize)
|
|
||||||
|
|
||||||
if bytesRead < 0 { continue }
|
|
||||||
|
|
||||||
buffer.append(Data(bytes: &bytes, count: bytesRead))
|
|
||||||
|
|
||||||
totalBytesRead += bytesRead
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextMessage() -> Data? {
|
|
||||||
objc_sync_enter(self)
|
|
||||||
defer { objc_sync_exit(self) }
|
|
||||||
|
|
||||||
let headerSize = MemoryLayout<UInt32>.size
|
|
||||||
guard buffer.count - readPosition >= headerSize else { return nil }
|
|
||||||
let header = buffer.withUnsafeBytes({ (pointer: UnsafePointer<Int8>) -> UInt32 in
|
|
||||||
return pointer.advanced(by: self.readPosition).withMemoryRebound(to: UInt32.self, capacity: 1, { $0.pointee })
|
|
||||||
})
|
|
||||||
|
|
||||||
let payloadSize = Int(CFSwapInt32BigToHost(header))
|
|
||||||
|
|
||||||
readPosition += headerSize
|
|
||||||
|
|
||||||
guard buffer.count >= readPosition + payloadSize, buffer.count - readPosition >= payloadSize, payloadSize >= 0 else {
|
|
||||||
// Message hasn't arrived
|
|
||||||
readPosition -= headerSize
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let payload = buffer.withUnsafeBytes({ (pointer: UnsafePointer<Int8>) -> Data in
|
|
||||||
return Data(bytes: pointer.advanced(by: self.readPosition), count: payloadSize)
|
|
||||||
})
|
|
||||||
readPosition += payloadSize
|
|
||||||
|
|
||||||
resetBufferIfNeeded()
|
|
||||||
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
|
|
||||||
private func resetBufferIfNeeded() {
|
|
||||||
guard buffer.count >= maxBufferLength else { return }
|
|
||||||
|
|
||||||
if readPosition == buffer.count {
|
|
||||||
buffer = Data(capacity: maxBufferLength)
|
|
||||||
} else {
|
|
||||||
buffer = buffer.advanced(by: readPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
readPosition = 0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,618 +0,0 @@
|
||||||
// DO NOT EDIT.
|
|
||||||
//
|
|
||||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
|
||||||
// Source: cast_channel.proto
|
|
||||||
//
|
|
||||||
// For information on using the generated types, please see the documenation:
|
|
||||||
// https://github.com/apple/swift-protobuf/
|
|
||||||
|
|
||||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftProtobuf
|
|
||||||
|
|
||||||
// If the compiler emits an error on this type, it is because this file
|
|
||||||
// was generated by a version of the `protoc` Swift plug-in that is
|
|
||||||
// incompatible with the version of SwiftProtobuf to which you are linking.
|
|
||||||
// Please ensure that your are building against the same version of the API
|
|
||||||
// that was used to generate this file.
|
|
||||||
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
|
||||||
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
|
||||||
typealias Version = _2
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Extensions_Api_CastChannel_CastMessage: SwiftProtobuf.Message {
|
|
||||||
static let protoMessageName: String = _protobuf_package + ".CastMessage"
|
|
||||||
|
|
||||||
var protocolVersion: Extensions_Api_CastChannel_CastMessage.ProtocolVersion {
|
|
||||||
get {return _protocolVersion ?? .castv210}
|
|
||||||
set {_protocolVersion = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `protocolVersion` has been explicitly set.
|
|
||||||
var hasProtocolVersion: Bool {return self._protocolVersion != nil}
|
|
||||||
/// Clears the value of `protocolVersion`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearProtocolVersion() {self._protocolVersion = nil}
|
|
||||||
|
|
||||||
/// source and destination ids identify the origin and destination of the
|
|
||||||
/// message. They are used to route messages between endpoints that share a
|
|
||||||
/// device-to-device channel.
|
|
||||||
///
|
|
||||||
/// For messages between applications:
|
|
||||||
/// - The sender application id is a unique identifier generated on behalf of
|
|
||||||
/// the sender application.
|
|
||||||
/// - The receiver id is always the the session id for the application.
|
|
||||||
///
|
|
||||||
/// For messages to or from the sender or receiver platform, the special ids
|
|
||||||
/// 'sender-0' and 'receiver-0' can be used.
|
|
||||||
///
|
|
||||||
/// For messages intended for all endpoints using a given channel, the
|
|
||||||
/// wildcard destination_id '*' can be used.
|
|
||||||
var sourceID: String {
|
|
||||||
get {return _sourceID ?? String()}
|
|
||||||
set {_sourceID = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `sourceID` has been explicitly set.
|
|
||||||
var hasSourceID: Bool {return self._sourceID != nil}
|
|
||||||
/// Clears the value of `sourceID`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearSourceID() {self._sourceID = nil}
|
|
||||||
|
|
||||||
var destinationID: String {
|
|
||||||
get {return _destinationID ?? String()}
|
|
||||||
set {_destinationID = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `destinationID` has been explicitly set.
|
|
||||||
var hasDestinationID: Bool {return self._destinationID != nil}
|
|
||||||
/// Clears the value of `destinationID`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearDestinationID() {self._destinationID = nil}
|
|
||||||
|
|
||||||
/// This is the core multiplexing key. All messages are sent on a namespace
|
|
||||||
/// and endpoints sharing a channel listen on one or more namespaces. The
|
|
||||||
/// namespace defines the protocol and semantics of the message.
|
|
||||||
var namespace: String {
|
|
||||||
get {return _namespace ?? String()}
|
|
||||||
set {_namespace = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `namespace` has been explicitly set.
|
|
||||||
var hasNamespace: Bool {return self._namespace != nil}
|
|
||||||
/// Clears the value of `namespace`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearNamespace() {self._namespace = nil}
|
|
||||||
|
|
||||||
var payloadType: Extensions_Api_CastChannel_CastMessage.PayloadType {
|
|
||||||
get {return _payloadType ?? .string}
|
|
||||||
set {_payloadType = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `payloadType` has been explicitly set.
|
|
||||||
var hasPayloadType: Bool {return self._payloadType != nil}
|
|
||||||
/// Clears the value of `payloadType`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearPayloadType() {self._payloadType = nil}
|
|
||||||
|
|
||||||
/// Depending on payload_type, exactly one of the following optional fields
|
|
||||||
/// will always be set.
|
|
||||||
var payloadUtf8: String {
|
|
||||||
get {return _payloadUtf8 ?? String()}
|
|
||||||
set {_payloadUtf8 = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `payloadUtf8` has been explicitly set.
|
|
||||||
var hasPayloadUtf8: Bool {return self._payloadUtf8 != nil}
|
|
||||||
/// Clears the value of `payloadUtf8`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearPayloadUtf8() {self._payloadUtf8 = nil}
|
|
||||||
|
|
||||||
var payloadBinary: Data {
|
|
||||||
get {return _payloadBinary ?? SwiftProtobuf.Internal.emptyData}
|
|
||||||
set {_payloadBinary = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `payloadBinary` has been explicitly set.
|
|
||||||
var hasPayloadBinary: Bool {return self._payloadBinary != nil}
|
|
||||||
/// Clears the value of `payloadBinary`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearPayloadBinary() {self._payloadBinary = nil}
|
|
||||||
|
|
||||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
|
||||||
|
|
||||||
/// Always pass a version of the protocol for future compatibility
|
|
||||||
/// requirements.
|
|
||||||
enum ProtocolVersion: SwiftProtobuf.Enum {
|
|
||||||
typealias RawValue = Int
|
|
||||||
case castv210 // = 0
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self = .castv210
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(rawValue: Int) {
|
|
||||||
switch rawValue {
|
|
||||||
case 0: self = .castv210
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawValue: Int {
|
|
||||||
switch self {
|
|
||||||
case .castv210: return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What type of data do we have in this message.
|
|
||||||
enum PayloadType: SwiftProtobuf.Enum {
|
|
||||||
typealias RawValue = Int
|
|
||||||
case string // = 0
|
|
||||||
case binary // = 1
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self = .string
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(rawValue: Int) {
|
|
||||||
switch rawValue {
|
|
||||||
case 0: self = .string
|
|
||||||
case 1: self = .binary
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawValue: Int {
|
|
||||||
switch self {
|
|
||||||
case .string: return 0
|
|
||||||
case .binary: return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
public var isInitialized: Bool {
|
|
||||||
if self._protocolVersion == nil {return false}
|
|
||||||
if self._sourceID == nil {return false}
|
|
||||||
if self._destinationID == nil {return false}
|
|
||||||
if self._namespace == nil {return false}
|
|
||||||
if self._payloadType == nil {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the decoding initializers in the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `init(serializedData:)`, `init(jsonUTF8Data:)`, and other decoding
|
|
||||||
/// initializers are defined in the SwiftProtobuf library. See the Message and
|
|
||||||
/// Message+*Additions` files.
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
|
||||||
switch fieldNumber {
|
|
||||||
case 1: try decoder.decodeSingularEnumField(value: &self._protocolVersion)
|
|
||||||
case 2: try decoder.decodeSingularStringField(value: &self._sourceID)
|
|
||||||
case 3: try decoder.decodeSingularStringField(value: &self._destinationID)
|
|
||||||
case 4: try decoder.decodeSingularStringField(value: &self._namespace)
|
|
||||||
case 5: try decoder.decodeSingularEnumField(value: &self._payloadType)
|
|
||||||
case 6: try decoder.decodeSingularStringField(value: &self._payloadUtf8)
|
|
||||||
case 7: try decoder.decodeSingularBytesField(value: &self._payloadBinary)
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the encoding methods of the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `Message.serializedData()`, `Message.jsonUTF8Data()`, and
|
|
||||||
/// other serializer methods are defined in the SwiftProtobuf library. See the
|
|
||||||
/// `Message` and `Message+*Additions` files.
|
|
||||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
|
||||||
if let v = self._protocolVersion {
|
|
||||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
|
|
||||||
}
|
|
||||||
if let v = self._sourceID {
|
|
||||||
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
|
|
||||||
}
|
|
||||||
if let v = self._destinationID {
|
|
||||||
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
|
|
||||||
}
|
|
||||||
if let v = self._namespace {
|
|
||||||
try visitor.visitSingularStringField(value: v, fieldNumber: 4)
|
|
||||||
}
|
|
||||||
if let v = self._payloadType {
|
|
||||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 5)
|
|
||||||
}
|
|
||||||
if let v = self._payloadUtf8 {
|
|
||||||
try visitor.visitSingularStringField(value: v, fieldNumber: 6)
|
|
||||||
}
|
|
||||||
if let v = self._payloadBinary {
|
|
||||||
try visitor.visitSingularBytesField(value: v, fieldNumber: 7)
|
|
||||||
}
|
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var _protocolVersion: Extensions_Api_CastChannel_CastMessage.ProtocolVersion?
|
|
||||||
fileprivate var _sourceID: String?
|
|
||||||
fileprivate var _destinationID: String?
|
|
||||||
fileprivate var _namespace: String?
|
|
||||||
fileprivate var _payloadType: Extensions_Api_CastChannel_CastMessage.PayloadType?
|
|
||||||
fileprivate var _payloadUtf8: String?
|
|
||||||
fileprivate var _payloadBinary: Data?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Messages for authentication protocol between a sender and a receiver.
|
|
||||||
struct Extensions_Api_CastChannel_AuthChallenge: SwiftProtobuf.Message {
|
|
||||||
static let protoMessageName: String = _protobuf_package + ".AuthChallenge"
|
|
||||||
|
|
||||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
/// Used by the decoding initializers in the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `init(serializedData:)`, `init(jsonUTF8Data:)`, and other decoding
|
|
||||||
/// initializers are defined in the SwiftProtobuf library. See the Message and
|
|
||||||
/// Message+*Additions` files.
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
|
||||||
while let _ = try decoder.nextFieldNumber() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the encoding methods of the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `Message.serializedData()`, `Message.jsonUTF8Data()`, and
|
|
||||||
/// other serializer methods are defined in the SwiftProtobuf library. See the
|
|
||||||
/// `Message` and `Message+*Additions` files.
|
|
||||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Extensions_Api_CastChannel_AuthResponse: SwiftProtobuf.Message {
|
|
||||||
static let protoMessageName: String = _protobuf_package + ".AuthResponse"
|
|
||||||
|
|
||||||
var signature: Data {
|
|
||||||
get {return _signature ?? SwiftProtobuf.Internal.emptyData}
|
|
||||||
set {_signature = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `signature` has been explicitly set.
|
|
||||||
var hasSignature: Bool {return self._signature != nil}
|
|
||||||
/// Clears the value of `signature`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearSignature() {self._signature = nil}
|
|
||||||
|
|
||||||
var clientAuthCertificate: Data {
|
|
||||||
get {return _clientAuthCertificate ?? SwiftProtobuf.Internal.emptyData}
|
|
||||||
set {_clientAuthCertificate = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `clientAuthCertificate` has been explicitly set.
|
|
||||||
var hasClientAuthCertificate: Bool {return self._clientAuthCertificate != nil}
|
|
||||||
/// Clears the value of `clientAuthCertificate`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearClientAuthCertificate() {self._clientAuthCertificate = nil}
|
|
||||||
|
|
||||||
var clientCa: [Data] = []
|
|
||||||
|
|
||||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
public var isInitialized: Bool {
|
|
||||||
if self._signature == nil {return false}
|
|
||||||
if self._clientAuthCertificate == nil {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the decoding initializers in the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `init(serializedData:)`, `init(jsonUTF8Data:)`, and other decoding
|
|
||||||
/// initializers are defined in the SwiftProtobuf library. See the Message and
|
|
||||||
/// Message+*Additions` files.
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
|
||||||
switch fieldNumber {
|
|
||||||
case 1: try decoder.decodeSingularBytesField(value: &self._signature)
|
|
||||||
case 2: try decoder.decodeSingularBytesField(value: &self._clientAuthCertificate)
|
|
||||||
case 3: try decoder.decodeRepeatedBytesField(value: &self.clientCa)
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the encoding methods of the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `Message.serializedData()`, `Message.jsonUTF8Data()`, and
|
|
||||||
/// other serializer methods are defined in the SwiftProtobuf library. See the
|
|
||||||
/// `Message` and `Message+*Additions` files.
|
|
||||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
|
||||||
if let v = self._signature {
|
|
||||||
try visitor.visitSingularBytesField(value: v, fieldNumber: 1)
|
|
||||||
}
|
|
||||||
if let v = self._clientAuthCertificate {
|
|
||||||
try visitor.visitSingularBytesField(value: v, fieldNumber: 2)
|
|
||||||
}
|
|
||||||
if !self.clientCa.isEmpty {
|
|
||||||
try visitor.visitRepeatedBytesField(value: self.clientCa, fieldNumber: 3)
|
|
||||||
}
|
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var _signature: Data?
|
|
||||||
fileprivate var _clientAuthCertificate: Data?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Extensions_Api_CastChannel_AuthError: SwiftProtobuf.Message {
|
|
||||||
static let protoMessageName: String = _protobuf_package + ".AuthError"
|
|
||||||
|
|
||||||
var errorType: Extensions_Api_CastChannel_AuthError.ErrorType {
|
|
||||||
get {return _errorType ?? .internalError}
|
|
||||||
set {_errorType = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `errorType` has been explicitly set.
|
|
||||||
var hasErrorType: Bool {return self._errorType != nil}
|
|
||||||
/// Clears the value of `errorType`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearErrorType() {self._errorType = nil}
|
|
||||||
|
|
||||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
|
||||||
|
|
||||||
enum ErrorType: SwiftProtobuf.Enum {
|
|
||||||
typealias RawValue = Int
|
|
||||||
case internalError // = 0
|
|
||||||
|
|
||||||
/// The underlying connection is not TLS
|
|
||||||
case noTls // = 1
|
|
||||||
|
|
||||||
init() {
|
|
||||||
self = .internalError
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(rawValue: Int) {
|
|
||||||
switch rawValue {
|
|
||||||
case 0: self = .internalError
|
|
||||||
case 1: self = .noTls
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawValue: Int {
|
|
||||||
switch self {
|
|
||||||
case .internalError: return 0
|
|
||||||
case .noTls: return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
public var isInitialized: Bool {
|
|
||||||
if self._errorType == nil {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the decoding initializers in the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `init(serializedData:)`, `init(jsonUTF8Data:)`, and other decoding
|
|
||||||
/// initializers are defined in the SwiftProtobuf library. See the Message and
|
|
||||||
/// Message+*Additions` files.
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
|
||||||
switch fieldNumber {
|
|
||||||
case 1: try decoder.decodeSingularEnumField(value: &self._errorType)
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the encoding methods of the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `Message.serializedData()`, `Message.jsonUTF8Data()`, and
|
|
||||||
/// other serializer methods are defined in the SwiftProtobuf library. See the
|
|
||||||
/// `Message` and `Message+*Additions` files.
|
|
||||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
|
||||||
if let v = self._errorType {
|
|
||||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
|
|
||||||
}
|
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var _errorType: Extensions_Api_CastChannel_AuthError.ErrorType?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Extensions_Api_CastChannel_DeviceAuthMessage: SwiftProtobuf.Message {
|
|
||||||
static let protoMessageName: String = _protobuf_package + ".DeviceAuthMessage"
|
|
||||||
|
|
||||||
/// Request fields
|
|
||||||
var challenge: Extensions_Api_CastChannel_AuthChallenge {
|
|
||||||
get {return _storage._challenge ?? Extensions_Api_CastChannel_AuthChallenge()}
|
|
||||||
set {_uniqueStorage()._challenge = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `challenge` has been explicitly set.
|
|
||||||
var hasChallenge: Bool {return _storage._challenge != nil}
|
|
||||||
/// Clears the value of `challenge`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearChallenge() {_storage._challenge = nil}
|
|
||||||
|
|
||||||
/// Response fields
|
|
||||||
var response: Extensions_Api_CastChannel_AuthResponse {
|
|
||||||
get {return _storage._response ?? Extensions_Api_CastChannel_AuthResponse()}
|
|
||||||
set {_uniqueStorage()._response = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `response` has been explicitly set.
|
|
||||||
var hasResponse: Bool {return _storage._response != nil}
|
|
||||||
/// Clears the value of `response`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearResponse() {_storage._response = nil}
|
|
||||||
|
|
||||||
var error: Extensions_Api_CastChannel_AuthError {
|
|
||||||
get {return _storage._error ?? Extensions_Api_CastChannel_AuthError()}
|
|
||||||
set {_uniqueStorage()._error = newValue}
|
|
||||||
}
|
|
||||||
/// Returns true if `error` has been explicitly set.
|
|
||||||
var hasError: Bool {return _storage._error != nil}
|
|
||||||
/// Clears the value of `error`. Subsequent reads from it will return its default value.
|
|
||||||
mutating func clearError() {_storage._error = nil}
|
|
||||||
|
|
||||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
public var isInitialized: Bool {
|
|
||||||
return withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
|
||||||
if let v = _storage._response, !v.isInitialized {return false}
|
|
||||||
if let v = _storage._error, !v.isInitialized {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the decoding initializers in the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `init(serializedData:)`, `init(jsonUTF8Data:)`, and other decoding
|
|
||||||
/// initializers are defined in the SwiftProtobuf library. See the Message and
|
|
||||||
/// Message+*Additions` files.
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
|
||||||
_ = _uniqueStorage()
|
|
||||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
|
||||||
switch fieldNumber {
|
|
||||||
case 1: try decoder.decodeSingularMessageField(value: &_storage._challenge)
|
|
||||||
case 2: try decoder.decodeSingularMessageField(value: &_storage._response)
|
|
||||||
case 3: try decoder.decodeSingularMessageField(value: &_storage._error)
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by the encoding methods of the SwiftProtobuf library, not generally
|
|
||||||
/// used directly. `Message.serializedData()`, `Message.jsonUTF8Data()`, and
|
|
||||||
/// other serializer methods are defined in the SwiftProtobuf library. See the
|
|
||||||
/// `Message` and `Message+*Additions` files.
|
|
||||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
|
||||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
|
||||||
if let v = _storage._challenge {
|
|
||||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
|
|
||||||
}
|
|
||||||
if let v = _storage._response {
|
|
||||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
|
||||||
}
|
|
||||||
if let v = _storage._error {
|
|
||||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate var _storage = _StorageClass.defaultInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
|
||||||
|
|
||||||
private let _protobuf_package = "extensions.api.cast_channel"
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_CastMessage: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
|
||||||
1: .standard(proto: "protocol_version"),
|
|
||||||
2: .standard(proto: "source_id"),
|
|
||||||
3: .standard(proto: "destination_id"),
|
|
||||||
4: .same(proto: "namespace"),
|
|
||||||
5: .standard(proto: "payload_type"),
|
|
||||||
6: .standard(proto: "payload_utf8"),
|
|
||||||
7: .standard(proto: "payload_binary")
|
|
||||||
]
|
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_CastMessage) -> Bool {
|
|
||||||
if self._protocolVersion != other._protocolVersion {return false}
|
|
||||||
if self._sourceID != other._sourceID {return false}
|
|
||||||
if self._destinationID != other._destinationID {return false}
|
|
||||||
if self._namespace != other._namespace {return false}
|
|
||||||
if self._payloadType != other._payloadType {return false}
|
|
||||||
if self._payloadUtf8 != other._payloadUtf8 {return false}
|
|
||||||
if self._payloadBinary != other._payloadBinary {return false}
|
|
||||||
if unknownFields != other.unknownFields {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_CastMessage.ProtocolVersion: SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
|
||||||
0: .same(proto: "CASTV2_1_0")
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_CastMessage.PayloadType: SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
|
||||||
0: .same(proto: "STRING"),
|
|
||||||
1: .same(proto: "BINARY")
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_AuthChallenge: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap()
|
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_AuthChallenge) -> Bool {
|
|
||||||
if unknownFields != other.unknownFields {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_AuthResponse: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
|
||||||
1: .same(proto: "signature"),
|
|
||||||
2: .standard(proto: "client_auth_certificate"),
|
|
||||||
3: .standard(proto: "client_ca")
|
|
||||||
]
|
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_AuthResponse) -> Bool {
|
|
||||||
if self._signature != other._signature {return false}
|
|
||||||
if self._clientAuthCertificate != other._clientAuthCertificate {return false}
|
|
||||||
if self.clientCa != other.clientCa {return false}
|
|
||||||
if unknownFields != other.unknownFields {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_AuthError: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
|
||||||
1: .standard(proto: "error_type")
|
|
||||||
]
|
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_AuthError) -> Bool {
|
|
||||||
if self._errorType != other._errorType {return false}
|
|
||||||
if unknownFields != other.unknownFields {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_AuthError.ErrorType: SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
|
||||||
0: .same(proto: "INTERNAL_ERROR"),
|
|
||||||
1: .same(proto: "NO_TLS")
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_DeviceAuthMessage: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
|
||||||
1: .same(proto: "challenge"),
|
|
||||||
2: .same(proto: "response"),
|
|
||||||
3: .same(proto: "error")
|
|
||||||
]
|
|
||||||
|
|
||||||
fileprivate class _StorageClass {
|
|
||||||
var _challenge: Extensions_Api_CastChannel_AuthChallenge?
|
|
||||||
var _response: Extensions_Api_CastChannel_AuthResponse?
|
|
||||||
var _error: Extensions_Api_CastChannel_AuthError?
|
|
||||||
|
|
||||||
static let defaultInstance = _StorageClass()
|
|
||||||
|
|
||||||
private init() {}
|
|
||||||
|
|
||||||
init(copying source: _StorageClass) {
|
|
||||||
_challenge = source._challenge
|
|
||||||
_response = source._response
|
|
||||||
_error = source._error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate mutating func _uniqueStorage() -> _StorageClass {
|
|
||||||
if !isKnownUniquelyReferenced(&_storage) {
|
|
||||||
_storage = _StorageClass(copying: _storage)
|
|
||||||
}
|
|
||||||
return _storage
|
|
||||||
}
|
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_DeviceAuthMessage) -> Bool {
|
|
||||||
if _storage !== other._storage {
|
|
||||||
let storagesAreEqual: Bool = withExtendedLifetime((_storage, other._storage)) { (_args: (_StorageClass, _StorageClass)) in
|
|
||||||
let _storage = _args.0
|
|
||||||
let other_storage = _args.1
|
|
||||||
if _storage._challenge != other_storage._challenge {return false}
|
|
||||||
if _storage._response != other_storage._response {return false}
|
|
||||||
if _storage._error != other_storage._error {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !storagesAreEqual {return false}
|
|
||||||
}
|
|
||||||
if unknownFields != other.unknownFields {return false}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
syntax = "proto2";
|
|
||||||
|
|
||||||
option optimize_for = LITE_RUNTIME;
|
|
||||||
|
|
||||||
package extensions.api.cast_channel;
|
|
||||||
|
|
||||||
message CastMessage {
|
|
||||||
// Always pass a version of the protocol for future compatibility
|
|
||||||
// requirements.
|
|
||||||
enum ProtocolVersion {
|
|
||||||
CASTV2_1_0 = 0;
|
|
||||||
}
|
|
||||||
required ProtocolVersion protocol_version = 1;
|
|
||||||
|
|
||||||
// source and destination ids identify the origin and destination of the
|
|
||||||
// message. They are used to route messages between endpoints that share a
|
|
||||||
// device-to-device channel.
|
|
||||||
//
|
|
||||||
// For messages between applications:
|
|
||||||
// - The sender application id is a unique identifier generated on behalf of
|
|
||||||
// the sender application.
|
|
||||||
// - The receiver id is always the the session id for the application.
|
|
||||||
//
|
|
||||||
// For messages to or from the sender or receiver platform, the special ids
|
|
||||||
// 'sender-0' and 'receiver-0' can be used.
|
|
||||||
//
|
|
||||||
// For messages intended for all endpoints using a given channel, the
|
|
||||||
// wildcard destination_id '*' can be used.
|
|
||||||
required string source_id = 2;
|
|
||||||
required string destination_id = 3;
|
|
||||||
|
|
||||||
// This is the core multiplexing key. All messages are sent on a namespace
|
|
||||||
// and endpoints sharing a channel listen on one or more namespaces. The
|
|
||||||
// namespace defines the protocol and semantics of the message.
|
|
||||||
required string namespace = 4;
|
|
||||||
|
|
||||||
// Encoding and payload info follows.
|
|
||||||
|
|
||||||
// What type of data do we have in this message.
|
|
||||||
enum PayloadType {
|
|
||||||
STRING = 0;
|
|
||||||
BINARY = 1;
|
|
||||||
}
|
|
||||||
required PayloadType payload_type = 5;
|
|
||||||
|
|
||||||
// Depending on payload_type, exactly one of the following optional fields
|
|
||||||
// will always be set.
|
|
||||||
optional string payload_utf8 = 6;
|
|
||||||
optional bytes payload_binary = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Messages for authentication protocol between a sender and a receiver.
|
|
||||||
message AuthChallenge {
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthResponse {
|
|
||||||
required bytes signature = 1;
|
|
||||||
required bytes client_auth_certificate = 2;
|
|
||||||
repeated bytes client_ca = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthError {
|
|
||||||
enum ErrorType {
|
|
||||||
INTERNAL_ERROR = 0;
|
|
||||||
NO_TLS = 1; // The underlying connection is not TLS
|
|
||||||
}
|
|
||||||
required ErrorType error_type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeviceAuthMessage {
|
|
||||||
// Request fields
|
|
||||||
optional AuthChallenge challenge = 1;
|
|
||||||
// Response fields
|
|
||||||
optional AuthResponse response = 2;
|
|
||||||
optional AuthError error = 3;
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
//
|
|
||||||
// AppAvailability.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftyJSON
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public class AppAvailability: NSObject {
|
|
||||||
public var availability = [String: Bool]()
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppAvailability {
|
|
||||||
convenience init(json: JSON) {
|
|
||||||
self.init()
|
|
||||||
|
|
||||||
if let availability = json[CastJSONPayloadKeys.availability].dictionaryObject as? [String: String] {
|
|
||||||
self.availability = availability.mapValues { $0 == "APP_AVAILABLE" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
//
|
|
||||||
// CastApp.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
public struct CastAppIdentifier {
|
|
||||||
public static let defaultMediaPlayer = "CC1AD845"
|
|
||||||
public static let youTube = "YouTube"
|
|
||||||
public static let googleAssistant = "97216CB6"
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class CastApp: NSObject {
|
|
||||||
public var id: String = ""
|
|
||||||
public var displayName: String = ""
|
|
||||||
public var isIdleScreen: Bool = false
|
|
||||||
public var sessionId: String = ""
|
|
||||||
public var statusText: String = ""
|
|
||||||
public var transportId: String = ""
|
|
||||||
public var namespaces = [String]()
|
|
||||||
|
|
||||||
convenience init(json: JSON) {
|
|
||||||
self.init()
|
|
||||||
|
|
||||||
if let id = json[CastJSONPayloadKeys.appId].string {
|
|
||||||
self.id = id
|
|
||||||
}
|
|
||||||
|
|
||||||
if let displayName = json[CastJSONPayloadKeys.displayName].string {
|
|
||||||
self.displayName = displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
if let isIdleScreen = json[CastJSONPayloadKeys.isIdleScreen].bool {
|
|
||||||
self.isIdleScreen = isIdleScreen
|
|
||||||
}
|
|
||||||
|
|
||||||
if let sessionId = json[CastJSONPayloadKeys.sessionId].string {
|
|
||||||
self.sessionId = sessionId
|
|
||||||
}
|
|
||||||
|
|
||||||
if let statusText = json[CastJSONPayloadKeys.statusText].string {
|
|
||||||
self.statusText = statusText
|
|
||||||
}
|
|
||||||
|
|
||||||
if let transportId = json[CastJSONPayloadKeys.transportId].string {
|
|
||||||
self.transportId = transportId
|
|
||||||
}
|
|
||||||
|
|
||||||
if let namespaces = json[CastJSONPayloadKeys.namespaces].array {
|
|
||||||
self.namespaces = namespaces.compactMap { $0[CastJSONPayloadKeys.name].string }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override var description: String {
|
|
||||||
return "CastApp(id: \(id), displayName: \(displayName), isIdleScreen: \(isIdleScreen), sessionId: \(sessionId), statusText: \(statusText), transportId: \(transportId), namespaces: \(namespaces)"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
//
|
|
||||||
// CastDevice.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public struct DeviceCapabilities: OptionSet {
|
|
||||||
public let rawValue: Int
|
|
||||||
public init(rawValue: Int) { self.rawValue = rawValue }
|
|
||||||
|
|
||||||
public static let none = DeviceCapabilities([])
|
|
||||||
public static let videoOut = DeviceCapabilities(rawValue: 1 << 0)
|
|
||||||
public static let videoIn = DeviceCapabilities(rawValue: 1 << 1)
|
|
||||||
public static let audioOut = DeviceCapabilities(rawValue: 1 << 2)
|
|
||||||
public static let audioIn = DeviceCapabilities(rawValue: 1 << 3)
|
|
||||||
public static let multizoneGroup = DeviceCapabilities(rawValue: 1 << 5)
|
|
||||||
public static let masterVolume = DeviceCapabilities(rawValue: 1 << 11)
|
|
||||||
public static let attenuationVolume = DeviceCapabilities(rawValue: 1 << 12)
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class CastDevice: NSObject, NSCopying {
|
|
||||||
|
|
||||||
public private(set) var id: String
|
|
||||||
public private(set) var name: String
|
|
||||||
public private(set) var modelName: String
|
|
||||||
public private(set) var hostName: String
|
|
||||||
public private(set) var ipAddress: String
|
|
||||||
public private(set) var port: Int
|
|
||||||
public private(set) var capabilities: DeviceCapabilities
|
|
||||||
public private(set) var status: String
|
|
||||||
public private(set) var iconPath: String
|
|
||||||
|
|
||||||
init(id: String, name: String, modelName: String, hostName: String, ipAddress: String, port: Int, capabilitiesMask: Int, status: String, iconPath: String) {
|
|
||||||
self.id = id
|
|
||||||
self.name = name
|
|
||||||
self.modelName = modelName
|
|
||||||
self.hostName = hostName
|
|
||||||
self.ipAddress = ipAddress
|
|
||||||
self.port = port
|
|
||||||
capabilities = DeviceCapabilities(rawValue: capabilitiesMask)
|
|
||||||
self.status = status
|
|
||||||
self.iconPath = iconPath
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func copy(with zone: NSZone? = nil) -> Any {
|
|
||||||
return CastDevice(id: self.id,
|
|
||||||
name: self.name,
|
|
||||||
modelName: self.modelName,
|
|
||||||
hostName: self.hostName,
|
|
||||||
ipAddress: self.ipAddress,
|
|
||||||
port: self.port,
|
|
||||||
capabilitiesMask: capabilities.rawValue,
|
|
||||||
status: self.status,
|
|
||||||
iconPath: iconPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
public override var description: String {
|
|
||||||
return "CastDevice(id: \(id), name: \(name), hostName:\(hostName), ipAddress:\(ipAddress), port:\(port))"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
//
|
|
||||||
// CastMedia.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public let CastMediaStreamTypeBuffered = "BUFFERED"
|
|
||||||
public let CastMediaStreamTypeLive = "LIVE"
|
|
||||||
|
|
||||||
public enum CastMediaStreamType: String {
|
|
||||||
case buffered = "BUFFERED"
|
|
||||||
case live = "LIVE"
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class CastMedia: NSObject {
|
|
||||||
public let title: String
|
|
||||||
public let url: URL
|
|
||||||
public let poster: URL?
|
|
||||||
|
|
||||||
public let autoplay: Bool
|
|
||||||
public let currentTime: Double
|
|
||||||
|
|
||||||
public let contentType: String
|
|
||||||
public let streamType: CastMediaStreamType
|
|
||||||
|
|
||||||
public init(title: String, url: URL, poster: URL? = nil, contentType: String, streamType: CastMediaStreamType = .buffered, autoplay: Bool = true, currentTime: Double = 0) {
|
|
||||||
self.title = title
|
|
||||||
self.url = url
|
|
||||||
self.poster = poster
|
|
||||||
self.contentType = contentType
|
|
||||||
self.streamType = streamType
|
|
||||||
self.autoplay = autoplay
|
|
||||||
self.currentTime = currentTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// public convenience init(title: String, url: URL, poster: URL, contentType: String, streamType: String, autoplay: Bool, currentTime: Double) {
|
|
||||||
// guard let type = CastMediaStreamType(rawValue: streamType) else {
|
|
||||||
// fatalError("Invalid media stream type \(streamType)")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// self.init(title: title, url: url, poster: poster, contentType: contentType, streamType: type, autoplay: autoplay, currentTime: currentTime)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastMedia {
|
|
||||||
|
|
||||||
var dict: [String: Any] {
|
|
||||||
if let poster = poster {
|
|
||||||
return [
|
|
||||||
CastJSONPayloadKeys.autoplay: autoplay,
|
|
||||||
CastJSONPayloadKeys.activeTrackIds: [],
|
|
||||||
CastJSONPayloadKeys.repeatMode: "REPEAT_OFF",
|
|
||||||
CastJSONPayloadKeys.currentTime: currentTime,
|
|
||||||
CastJSONPayloadKeys.media: [
|
|
||||||
CastJSONPayloadKeys.contentId: url.absoluteString,
|
|
||||||
CastJSONPayloadKeys.contentType: contentType,
|
|
||||||
CastJSONPayloadKeys.streamType: streamType.rawValue,
|
|
||||||
CastJSONPayloadKeys.metadata: [
|
|
||||||
CastJSONPayloadKeys.type: 0,
|
|
||||||
CastJSONPayloadKeys.metadataType: 0,
|
|
||||||
CastJSONPayloadKeys.title: title,
|
|
||||||
CastJSONPayloadKeys.images: [
|
|
||||||
[CastJSONPayloadKeys.url: poster.absoluteString]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
CastJSONPayloadKeys.autoplay: autoplay,
|
|
||||||
CastJSONPayloadKeys.activeTrackIds: [],
|
|
||||||
CastJSONPayloadKeys.repeatMode: "REPEAT_OFF",
|
|
||||||
CastJSONPayloadKeys.currentTime: currentTime,
|
|
||||||
CastJSONPayloadKeys.media: [
|
|
||||||
CastJSONPayloadKeys.contentId: url.absoluteString,
|
|
||||||
CastJSONPayloadKeys.contentType: contentType,
|
|
||||||
CastJSONPayloadKeys.streamType: streamType.rawValue,
|
|
||||||
CastJSONPayloadKeys.metadata: [
|
|
||||||
CastJSONPayloadKeys.type: 0,
|
|
||||||
CastJSONPayloadKeys.metadataType: 0,
|
|
||||||
CastJSONPayloadKeys.title: title
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
//
|
|
||||||
// CastMediaStatus.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
public enum CastMediaPlayerState: String {
|
|
||||||
case buffering = "BUFFERING"
|
|
||||||
case playing = "PLAYING"
|
|
||||||
case paused = "PAUSED"
|
|
||||||
case stopped = "STOPPED"
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class CastMediaStatus: NSObject {
|
|
||||||
|
|
||||||
public let mediaSessionId: Int
|
|
||||||
public let playbackRate: Int
|
|
||||||
public let playerState: CastMediaPlayerState
|
|
||||||
public let currentTime: Double
|
|
||||||
public let metadata: JSON?
|
|
||||||
public let contentID: String?
|
|
||||||
private let createdDate = Date()
|
|
||||||
|
|
||||||
public var adjustedCurrentTime: Double {
|
|
||||||
return currentTime - Double(playbackRate)*createdDate.timeIntervalSinceNow
|
|
||||||
}
|
|
||||||
|
|
||||||
public var state: String {
|
|
||||||
return playerState.rawValue
|
|
||||||
}
|
|
||||||
|
|
||||||
public override var description: String {
|
|
||||||
return "MediaStatus(mediaSessionId: \(mediaSessionId), playbackRate: \(playbackRate), playerState: \(playerState.rawValue), currentTime: \(currentTime))"
|
|
||||||
}
|
|
||||||
|
|
||||||
init(json: JSON) {
|
|
||||||
mediaSessionId = json[CastJSONPayloadKeys.mediaSessionId].int ?? 0
|
|
||||||
|
|
||||||
playbackRate = json[CastJSONPayloadKeys.playbackRate].int ?? 1
|
|
||||||
|
|
||||||
playerState = json[CastJSONPayloadKeys.playerState].string.flatMap(CastMediaPlayerState.init) ?? .buffering
|
|
||||||
|
|
||||||
currentTime = json[CastJSONPayloadKeys.currentTime].double ?? 0
|
|
||||||
|
|
||||||
metadata = json[CastJSONPayloadKeys.media][CastJSONPayloadKeys.metadata]
|
|
||||||
|
|
||||||
if let contentID = json[CastJSONPayloadKeys.media][CastJSONPayloadKeys.contentId].string, let data = contentID.data(using: .utf8) {
|
|
||||||
self.contentID = (try? JSON(data: data))?[CastJSONPayloadKeys.contentId].string ?? contentID
|
|
||||||
} else {
|
|
||||||
contentID = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
//
|
|
||||||
// CastMessage.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension CastMessage {
|
|
||||||
static func encodedMessage(payload: CastPayload, namespace: String, sourceId: String, destinationId: String) throws -> Data {
|
|
||||||
var message = CastMessage()
|
|
||||||
message.protocolVersion = .castv210
|
|
||||||
message.sourceID = sourceId
|
|
||||||
message.destinationID = destinationId
|
|
||||||
message.namespace = namespace
|
|
||||||
|
|
||||||
switch payload {
|
|
||||||
case .json(let payload):
|
|
||||||
let json = try JSONSerialization.data(withJSONObject: payload, options: [])
|
|
||||||
|
|
||||||
guard let jsonString = String(data: json, encoding: .utf8) else {
|
|
||||||
fatalError("error forming json string")
|
|
||||||
}
|
|
||||||
|
|
||||||
message.payloadType = .string
|
|
||||||
message.payloadUtf8 = jsonString
|
|
||||||
case .data(let payload):
|
|
||||||
message.payloadType = .binary
|
|
||||||
message.payloadBinary = payload
|
|
||||||
}
|
|
||||||
|
|
||||||
return try message.serializedData()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
//
|
|
||||||
// CastMultizoneDevice.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
public class CastMultizoneDevice {
|
|
||||||
public let name: String
|
|
||||||
public let volume: Float
|
|
||||||
public let isMuted: Bool
|
|
||||||
public let capabilities: DeviceCapabilities
|
|
||||||
public let id: String
|
|
||||||
|
|
||||||
public init(name: String, volume: Float, isMuted: Bool, capabilitiesMask: Int, id: String) {
|
|
||||||
self.name = name
|
|
||||||
self.volume = volume
|
|
||||||
self.isMuted = isMuted
|
|
||||||
capabilities = DeviceCapabilities(rawValue: capabilitiesMask)
|
|
||||||
self.id = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastMultizoneDevice {
|
|
||||||
convenience init(json: JSON) {
|
|
||||||
let name = json[CastJSONPayloadKeys.name].stringValue
|
|
||||||
|
|
||||||
let volumeValues = json[CastJSONPayloadKeys.volume]
|
|
||||||
|
|
||||||
let volume = volumeValues[CastJSONPayloadKeys.level].floatValue
|
|
||||||
let isMuted = volumeValues[CastJSONPayloadKeys.muted].boolValue
|
|
||||||
let capabilitiesMask = json[CastJSONPayloadKeys.capabilities].intValue
|
|
||||||
let deviceId = json[CastJSONPayloadKeys.deviceId].stringValue
|
|
||||||
|
|
||||||
self.init(name: name, volume: volume, isMuted: isMuted, capabilitiesMask: capabilitiesMask, id: deviceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
//
|
|
||||||
// CastMultizoneStatus.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
public class CastMultizoneStatus {
|
|
||||||
public let devices: [CastMultizoneDevice]
|
|
||||||
|
|
||||||
public init(devices: [CastMultizoneDevice]) {
|
|
||||||
self.devices = devices
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastMultizoneStatus {
|
|
||||||
|
|
||||||
convenience init(json: JSON) {
|
|
||||||
let status = json[CastJSONPayloadKeys.status]
|
|
||||||
let devices = status[CastJSONPayloadKeys.devices].array?.map(CastMultizoneDevice.init) ?? []
|
|
||||||
|
|
||||||
self.init(devices: devices)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
//
|
|
||||||
// CastStatus.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
public final class CastStatus: NSObject {
|
|
||||||
|
|
||||||
public var volume: Double = 1
|
|
||||||
public var muted: Bool = false
|
|
||||||
public var apps: [CastApp] = []
|
|
||||||
|
|
||||||
public override var description: String {
|
|
||||||
return "CastStatus(volume: \(volume), muted: \(muted), apps: \(apps))"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastStatus {
|
|
||||||
|
|
||||||
convenience init(json: JSON) {
|
|
||||||
self.init()
|
|
||||||
// print(json)
|
|
||||||
let status = json[CastJSONPayloadKeys.status]
|
|
||||||
let volume = status[CastJSONPayloadKeys.volume]
|
|
||||||
|
|
||||||
if let volume = volume[CastJSONPayloadKeys.level].double {
|
|
||||||
self.volume = volume
|
|
||||||
}
|
|
||||||
if let muted = volume[CastJSONPayloadKeys.muted].bool {
|
|
||||||
self.muted = muted
|
|
||||||
}
|
|
||||||
|
|
||||||
if let apps = status[CastJSONPayloadKeys.applications].array {
|
|
||||||
self.apps = apps.compactMap(CastApp.init)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,626 +0,0 @@
|
||||||
//
|
|
||||||
// CastClient.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftProtobuf
|
|
||||||
import SwiftyJSON
|
|
||||||
import Result
|
|
||||||
|
|
||||||
public enum CastPayload {
|
|
||||||
case json([String: Any])
|
|
||||||
case data(Data)
|
|
||||||
|
|
||||||
init(_ json: [String: Any]) {
|
|
||||||
self = .json(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(_ data: Data) {
|
|
||||||
self = .data(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias CastMessage = Extensions_Api_CastChannel_CastMessage
|
|
||||||
public typealias CastResponseHandler = (Result<JSON, CastError>) -> Void
|
|
||||||
|
|
||||||
public enum CastError: Error {
|
|
||||||
case connection(String)
|
|
||||||
case write(String)
|
|
||||||
case session(String)
|
|
||||||
case request(String)
|
|
||||||
case launch(String)
|
|
||||||
case load(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CastRequest: NSObject {
|
|
||||||
var id: Int
|
|
||||||
var namespace: String
|
|
||||||
var destinationId: String
|
|
||||||
var payload: CastPayload
|
|
||||||
|
|
||||||
init(id: Int, namespace: String, destinationId: String, payload: [String: Any]) {
|
|
||||||
self.id = id
|
|
||||||
self.namespace = namespace
|
|
||||||
self.destinationId = destinationId
|
|
||||||
self.payload = CastPayload(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(id: Int, namespace: String, destinationId: String, payload: Data) {
|
|
||||||
self.id = id
|
|
||||||
self.namespace = namespace
|
|
||||||
self.destinationId = destinationId
|
|
||||||
self.payload = CastPayload(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc public protocol CastClientDelegate: AnyObject {
|
|
||||||
|
|
||||||
@objc optional func castClient(_ client: CastClient, willConnectTo device: CastDevice)
|
|
||||||
@objc optional func castClient(_ client: CastClient, didConnectTo device: CastDevice)
|
|
||||||
@objc optional func castClient(_ client: CastClient, didDisconnectFrom device: CastDevice)
|
|
||||||
@objc optional func castClient(_ client: CastClient, connectionTo device: CastDevice, didFailWith error: Error?)
|
|
||||||
|
|
||||||
@objc optional func castClient(_ client: CastClient, deviceStatusDidChange status: CastStatus)
|
|
||||||
@objc optional func castClient(_ client: CastClient, mediaStatusDidChange status: CastMediaStatus)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
|
||||||
|
|
||||||
public let device: CastDevice
|
|
||||||
public weak var delegate: CastClientDelegate?
|
|
||||||
public var connectedApp: CastApp?
|
|
||||||
|
|
||||||
public private(set) var currentStatus: CastStatus? {
|
|
||||||
didSet {
|
|
||||||
guard let status = currentStatus else { return }
|
|
||||||
|
|
||||||
if oldValue != status {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.delegate?.castClient?(self, deviceStatusDidChange: status)
|
|
||||||
self.statusDidChange?(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public private(set) var currentMediaStatus: CastMediaStatus? {
|
|
||||||
didSet {
|
|
||||||
guard let status = currentMediaStatus else { return }
|
|
||||||
|
|
||||||
if oldValue != status {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.delegate?.castClient?(self, mediaStatusDidChange: status)
|
|
||||||
self.mediaStatusDidChange?(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public private(set) var currentMultizoneStatus: CastMultizoneStatus?
|
|
||||||
|
|
||||||
public var statusDidChange: ((CastStatus) -> Void)?
|
|
||||||
public var mediaStatusDidChange: ((CastMediaStatus) -> Void)?
|
|
||||||
|
|
||||||
public init(device: CastDevice) {
|
|
||||||
self.device = device
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Socket Setup
|
|
||||||
|
|
||||||
public var isConnected = false {
|
|
||||||
didSet {
|
|
||||||
if oldValue != isConnected {
|
|
||||||
if isConnected {
|
|
||||||
DispatchQueue.main.async { self.delegate?.castClient?(self, didConnectTo: self.device) }
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async { self.delegate?.castClient?(self, didDisconnectFrom: self.device) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var inputStream: InputStream! {
|
|
||||||
didSet {
|
|
||||||
if let inputStream = inputStream {
|
|
||||||
reader = CastV2PlatformReader(stream: inputStream)
|
|
||||||
} else {
|
|
||||||
reader = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var outputStream: OutputStream!
|
|
||||||
|
|
||||||
fileprivate lazy var socketQueue = DispatchQueue.global(qos: .userInitiated)
|
|
||||||
|
|
||||||
public func connect() {
|
|
||||||
socketQueue.async {
|
|
||||||
do {
|
|
||||||
var readStream: Unmanaged<CFReadStream>?
|
|
||||||
var writeStream: Unmanaged<CFWriteStream>?
|
|
||||||
|
|
||||||
let settings: [String: Any] = [
|
|
||||||
kCFStreamSSLValidatesCertificateChain as String: false,
|
|
||||||
kCFStreamSSLLevel as String: kCFStreamSocketSecurityLevelTLSv1,
|
|
||||||
kCFStreamPropertyShouldCloseNativeSocket as String: true
|
|
||||||
]
|
|
||||||
|
|
||||||
CFStreamCreatePairWithSocketToHost(nil, self.device.hostName as CFString, UInt32(self.device.port), &readStream, &writeStream)
|
|
||||||
|
|
||||||
guard let readStreamRetained = readStream?.takeRetainedValue() else {
|
|
||||||
throw CastError.connection("Unable to create input stream")
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let writeStreamRetained = writeStream?.takeRetainedValue() else {
|
|
||||||
throw CastError.connection("Unable to create output stream")
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async { self.delegate?.castClient?(self, willConnectTo: self.device) }
|
|
||||||
|
|
||||||
CFReadStreamSetProperty(readStreamRetained, CFStreamPropertyKey(kCFStreamPropertySSLSettings), settings as CFTypeRef?)
|
|
||||||
CFWriteStreamSetProperty(writeStreamRetained, CFStreamPropertyKey(kCFStreamPropertySSLSettings), settings as CFTypeRef?)
|
|
||||||
|
|
||||||
self.inputStream = readStreamRetained
|
|
||||||
self.outputStream = writeStreamRetained
|
|
||||||
|
|
||||||
self.inputStream.delegate = self
|
|
||||||
|
|
||||||
self.inputStream.schedule(in: .current, forMode: RunLoop.Mode.default)
|
|
||||||
self.outputStream.schedule(in: .current, forMode: RunLoop.Mode.default)
|
|
||||||
|
|
||||||
self.inputStream.open()
|
|
||||||
self.outputStream.open()
|
|
||||||
|
|
||||||
RunLoop.current.run()
|
|
||||||
} catch {
|
|
||||||
DispatchQueue.main.async { self.delegate?.castClient?(self, connectionTo: self.device, didFailWith: error as NSError) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func disconnect() {
|
|
||||||
if isConnected {
|
|
||||||
isConnected = false
|
|
||||||
}
|
|
||||||
|
|
||||||
channels.values.forEach(remove)
|
|
||||||
|
|
||||||
socketQueue.async {
|
|
||||||
if self.inputStream != nil {
|
|
||||||
self.inputStream.close()
|
|
||||||
self.inputStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
|
|
||||||
self.inputStream = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.outputStream != nil {
|
|
||||||
self.outputStream.close()
|
|
||||||
self.outputStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
|
|
||||||
self.outputStream = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Socket Lifecycle
|
|
||||||
|
|
||||||
private func write(data: Data) throws {
|
|
||||||
var payloadSize = UInt32(data.count).bigEndian
|
|
||||||
let packet = NSMutableData(bytes: &payloadSize, length: MemoryLayout<UInt32>.size)
|
|
||||||
packet.append(data)
|
|
||||||
|
|
||||||
let streamBytes = packet.bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
|
||||||
|
|
||||||
if outputStream.write(streamBytes, maxLength: packet.length) < 0 {
|
|
||||||
if let error = outputStream.streamError {
|
|
||||||
throw CastError.write("Error writing \(packet.length) byte(s) to stream: \(error)")
|
|
||||||
} else {
|
|
||||||
throw CastError.write("Unknown error writing \(packet.length) byte(s) to stream")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func sendConnectMessage() throws {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
_ = connectionChannel
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
_ = self.receiverControlChannel
|
|
||||||
_ = self.mediaControlChannel
|
|
||||||
_ = self.heartbeatChannel
|
|
||||||
|
|
||||||
if self.device.capabilities.contains(.multizoneGroup) {
|
|
||||||
_ = self.multizoneControlChannel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var reader: CastV2PlatformReader?
|
|
||||||
|
|
||||||
fileprivate func readStream() {
|
|
||||||
do {
|
|
||||||
reader?.readStream()
|
|
||||||
|
|
||||||
while let payload = reader?.nextMessage() {
|
|
||||||
let message = try CastMessage(serializedData: payload)
|
|
||||||
|
|
||||||
guard let channel = channels[message.namespace] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch message.payloadType {
|
|
||||||
case .string:
|
|
||||||
if let messageData = message.payloadUtf8.data(using: .utf8) {
|
|
||||||
let json = JSON(messageData)
|
|
||||||
|
|
||||||
channel.handleResponse(json,
|
|
||||||
sourceId: message.sourceID)
|
|
||||||
|
|
||||||
if let requestId = json[CastJSONPayloadKeys.requestId].int {
|
|
||||||
callResponseHandler(for: requestId, with: Result(value: json))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NSLog("Unable to get UTF8 JSON data from message")
|
|
||||||
}
|
|
||||||
case .binary:
|
|
||||||
channel.handleResponse(message.payloadBinary,
|
|
||||||
sourceId: message.sourceID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
NSLog("Error reading: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Channelable
|
|
||||||
|
|
||||||
var channels = [String: CastChannel]()
|
|
||||||
|
|
||||||
private lazy var heartbeatChannel: HeartbeatChannel = {
|
|
||||||
let channel = HeartbeatChannel()
|
|
||||||
self.add(channel: channel)
|
|
||||||
|
|
||||||
return channel
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var connectionChannel: DeviceConnectionChannel = {
|
|
||||||
let channel = DeviceConnectionChannel()
|
|
||||||
self.add(channel: channel)
|
|
||||||
|
|
||||||
return channel
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var receiverControlChannel: ReceiverControlChannel = {
|
|
||||||
let channel = ReceiverControlChannel()
|
|
||||||
self.add(channel: channel)
|
|
||||||
|
|
||||||
return channel
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var mediaControlChannel: MediaControlChannel = {
|
|
||||||
let channel = MediaControlChannel()
|
|
||||||
self.add(channel: channel)
|
|
||||||
|
|
||||||
return channel
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var multizoneControlChannel: MultizoneControlChannel = {
|
|
||||||
let channel = MultizoneControlChannel()
|
|
||||||
self.add(channel: channel)
|
|
||||||
|
|
||||||
return channel
|
|
||||||
}()
|
|
||||||
|
|
||||||
// MARK: - Request response
|
|
||||||
|
|
||||||
private lazy var currentRequestId = Int(arc4random_uniform(800))
|
|
||||||
|
|
||||||
func nextRequestId() -> Int {
|
|
||||||
currentRequestId += 1
|
|
||||||
|
|
||||||
return currentRequestId
|
|
||||||
}
|
|
||||||
|
|
||||||
private let senderName: String = "sender-\(UUID().uuidString)"
|
|
||||||
|
|
||||||
private var responseHandlers = [Int: CastResponseHandler]()
|
|
||||||
|
|
||||||
func send(_ request: CastRequest, response: CastResponseHandler?) {
|
|
||||||
if let response = response {
|
|
||||||
responseHandlers[request.id] = response
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
let messageData = try CastMessage.encodedMessage(payload: request.payload,
|
|
||||||
namespace: request.namespace,
|
|
||||||
sourceId: senderName,
|
|
||||||
destinationId: request.destinationId)
|
|
||||||
try write(data: messageData)
|
|
||||||
} catch {
|
|
||||||
callResponseHandler(for: request.id, with: Result(error: .request(error.localizedDescription)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func callResponseHandler(for requestId: Int, with result: Result<JSON, CastError>) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if let handler = self.responseHandlers.removeValue(forKey: requestId) {
|
|
||||||
handler(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Public messages
|
|
||||||
|
|
||||||
public func getAppAvailability(apps: [CastApp], completion: @escaping (Result<AppAvailability, CastError>) -> Void) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
receiverControlChannel.getAppAvailability(apps: apps, completion: completion)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func join(app: CastApp? = nil, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
|
||||||
guard outputStream != nil,
|
|
||||||
let target = app ?? currentStatus?.apps.first else {
|
|
||||||
completion(Result(error: CastError.session("No Apps Running")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if target == connectedApp {
|
|
||||||
completion(Result(value: target))
|
|
||||||
} else if let existing = currentStatus?.apps.first(where: { $0.id == target.id }) {
|
|
||||||
connect(to: existing)
|
|
||||||
completion(Result(value: existing))
|
|
||||||
} else {
|
|
||||||
receiverControlChannel.requestStatus { [weak self] result in
|
|
||||||
switch result {
|
|
||||||
case .success(let status):
|
|
||||||
guard let app = status.apps.first else {
|
|
||||||
completion(Result(error: CastError.launch("Unable to get launched app instance")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self?.connect(to: app)
|
|
||||||
completion(Result(value: app))
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
completion(Result(error: error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func launch(appId: String, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
receiverControlChannel.launch(appId: appId) { [weak self] result in
|
|
||||||
switch result {
|
|
||||||
case .success(let app):
|
|
||||||
self?.connect(to: app)
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
default:
|
|
||||||
completion(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func stopCurrentApp() {
|
|
||||||
guard outputStream != nil, let app = currentStatus?.apps.first else { return }
|
|
||||||
|
|
||||||
receiverControlChannel.stop(app: app)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func leave(_ app: CastApp) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
connectionChannel.leave(app)
|
|
||||||
connectedApp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
public func load(media: CastMedia, with app: CastApp, completion: @escaping (Result<CastMediaStatus, CastError>) -> Void) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
mediaControlChannel.load(media: media, with: app, completion: completion)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestMediaStatus(for app: CastApp, completion: ((Result<CastMediaStatus, CastError>) -> Void)? = nil) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
mediaControlChannel.requestMediaStatus(for: app)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func connect(to app: CastApp) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
connectionChannel.connect(to: app)
|
|
||||||
connectedApp = app
|
|
||||||
}
|
|
||||||
|
|
||||||
public func pause() {
|
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
|
||||||
mediaControlChannel.sendPause(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
} else {
|
|
||||||
mediaControlChannel.requestMediaStatus(for: app) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let mediaStatus):
|
|
||||||
self.mediaControlChannel.sendPause(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func play() {
|
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
|
||||||
mediaControlChannel.sendPlay(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
} else {
|
|
||||||
mediaControlChannel.requestMediaStatus(for: app) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let mediaStatus):
|
|
||||||
self.mediaControlChannel.sendPlay(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func stop() {
|
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
|
||||||
mediaControlChannel.sendStop(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
} else {
|
|
||||||
mediaControlChannel.requestMediaStatus(for: app) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let mediaStatus):
|
|
||||||
self.mediaControlChannel.sendStop(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func seek(to currentTime: Float) {
|
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
|
||||||
mediaControlChannel.sendSeek(to: currentTime, for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
} else {
|
|
||||||
mediaControlChannel.requestMediaStatus(for: app) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let mediaStatus):
|
|
||||||
self.mediaControlChannel.sendSeek(to: currentTime, for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setVolume(_ volume: Float) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
receiverControlChannel.setVolume(volume)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setMuted(_ muted: Bool) {
|
|
||||||
guard outputStream != nil else { return }
|
|
||||||
|
|
||||||
receiverControlChannel.setMuted(muted)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setVolume(_ volume: Float, for device: CastMultizoneDevice) {
|
|
||||||
guard device.capabilities.contains(.multizoneGroup) else {
|
|
||||||
print("Attempted to set zone volume on non-multizone device")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
multizoneControlChannel.setVolume(volume, for: device)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setMuted(_ isMuted: Bool, for device: CastMultizoneDevice) {
|
|
||||||
guard device.capabilities.contains(.multizoneGroup) else {
|
|
||||||
print("Attempted to mute zone on non-multizone device")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
multizoneControlChannel.setMuted(isMuted, for: device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastClient: StreamDelegate {
|
|
||||||
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
|
||||||
switch eventCode {
|
|
||||||
case Stream.Event.openCompleted:
|
|
||||||
guard !isConnected else { return }
|
|
||||||
socketQueue.async {
|
|
||||||
do {
|
|
||||||
try self.sendConnectMessage()
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
NSLog("Error sending connect message: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Stream.Event.errorOccurred:
|
|
||||||
NSLog("Stream error occurred: \(aStream.streamError.debugDescription)")
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.delegate?.castClient?(self, connectionTo: self.device, didFailWith: aStream.streamError)
|
|
||||||
}
|
|
||||||
case Stream.Event.hasBytesAvailable:
|
|
||||||
socketQueue.async {
|
|
||||||
self.readStream()
|
|
||||||
}
|
|
||||||
case Stream.Event.endEncountered:
|
|
||||||
NSLog("Input stream ended")
|
|
||||||
disconnect()
|
|
||||||
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastClient: ReceiverControlChannelDelegate {
|
|
||||||
func channel(_ channel: ReceiverControlChannel, didReceive status: CastStatus) {
|
|
||||||
currentStatus = status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastClient: MediaControlChannelDelegate {
|
|
||||||
func channel(_ channel: MediaControlChannel, didReceive mediaStatus: CastMediaStatus) {
|
|
||||||
currentMediaStatus = mediaStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastClient: HeartbeatChannelDelegate {
|
|
||||||
func channelDidConnect(_ channel: HeartbeatChannel) {
|
|
||||||
if !isConnected {
|
|
||||||
isConnected = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func channelDidTimeout(_ channel: HeartbeatChannel) {
|
|
||||||
disconnect()
|
|
||||||
currentStatus = nil
|
|
||||||
currentMediaStatus = nil
|
|
||||||
connectedApp = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastClient: MultizoneControlChannelDelegate {
|
|
||||||
func channel(_ channel: MultizoneControlChannel, added device: CastMultizoneDevice) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func channel(_ channel: MultizoneControlChannel, updated device: CastMultizoneDevice) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func channel(_ channel: MultizoneControlChannel, removed deviceId: String) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func channel(_ channel: MultizoneControlChannel, didReceive status: CastMultizoneStatus) {
|
|
||||||
currentMultizoneStatus = status
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,219 +0,0 @@
|
||||||
//
|
|
||||||
// CastDeviceScanner.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension CastDevice {
|
|
||||||
convenience init(service: NetService, info: [String: String]) {
|
|
||||||
var ipAddress: String?
|
|
||||||
|
|
||||||
if let address = service.addresses?.first {
|
|
||||||
ipAddress = address.withUnsafeBytes { (pointer: UnsafePointer<sockaddr>) -> String? in
|
|
||||||
var hostName = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
|
||||||
|
|
||||||
return getnameinfo(pointer, socklen_t(address.count), &hostName, socklen_t(NI_MAXHOST), nil, 0, NI_NUMERICHOST) == 0 ? String.init(cString: hostName) : nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init(id: info["id"] ?? "",
|
|
||||||
name: info["fn"] ?? service.name,
|
|
||||||
modelName: info["md"] ?? "Google Cast",
|
|
||||||
hostName: service.hostName!,
|
|
||||||
ipAddress: ipAddress ?? "",
|
|
||||||
port: service.port,
|
|
||||||
capabilitiesMask: info["ca"].flatMap(Int.init) ?? 0 ,
|
|
||||||
status: info["rs"] ?? "",
|
|
||||||
iconPath: info["ic"] ?? "")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class CastDeviceScanner: NSObject {
|
|
||||||
public weak var delegate: CastDeviceScannerDelegate?
|
|
||||||
|
|
||||||
public static let deviceListDidChange = Notification.Name(rawValue: "DeviceScannerDeviceListDidChangeNotification")
|
|
||||||
|
|
||||||
private lazy var browser: NetServiceBrowser = configureBrowser()
|
|
||||||
|
|
||||||
public var isScanning = false
|
|
||||||
|
|
||||||
fileprivate var services = [NetService]()
|
|
||||||
|
|
||||||
public fileprivate(set) var devices = [CastDevice]() {
|
|
||||||
didSet {
|
|
||||||
NotificationCenter.default.post(name: CastDeviceScanner.deviceListDidChange, object: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configureBrowser() -> NetServiceBrowser {
|
|
||||||
let b = NetServiceBrowser()
|
|
||||||
|
|
||||||
b.includesPeerToPeer = true
|
|
||||||
b.delegate = self
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
public func startScanning() {
|
|
||||||
guard !isScanning else { return }
|
|
||||||
|
|
||||||
browser.stop()
|
|
||||||
browser.searchForServices(ofType: "_googlecast._tcp", inDomain: "local")
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("Started scanning")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public func stopScanning() {
|
|
||||||
guard isScanning else { return }
|
|
||||||
|
|
||||||
browser.stop()
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("Stopped scanning")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public func reset() {
|
|
||||||
stopScanning()
|
|
||||||
devices.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
stopScanning()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastDeviceScanner: NetServiceBrowserDelegate {
|
|
||||||
|
|
||||||
public func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) {
|
|
||||||
isScanning = true
|
|
||||||
}
|
|
||||||
|
|
||||||
public func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
|
|
||||||
isScanning = false
|
|
||||||
}
|
|
||||||
|
|
||||||
public func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
|
|
||||||
removeService(service)
|
|
||||||
|
|
||||||
service.delegate = self
|
|
||||||
service.resolve(withTimeout: 30.0)
|
|
||||||
services.append(service)
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("Did find service: \(service) more: \(moreComing)")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
|
|
||||||
guard let service = removeService(service) else { return }
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("Did remove service: \(service)")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
guard let deviceId = service.id,
|
|
||||||
let index = devices.firstIndex(where: { $0.id == deviceId }) else {
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("No device")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("Removing device: \(devices[index])")
|
|
||||||
#endif
|
|
||||||
let device = devices.remove(at: index)
|
|
||||||
delegate?.deviceDidGoOffline(device)
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult func removeService(_ service: NetService) -> NetService? {
|
|
||||||
if let index = services.firstIndex(of: service) {
|
|
||||||
return services.remove(at: index)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addDevice(_ device: CastDevice) {
|
|
||||||
if let index = devices.firstIndex(where: { $0.id == device.id }) {
|
|
||||||
let existing = devices[index]
|
|
||||||
|
|
||||||
guard existing.name != device.name || existing.hostName != device.hostName else { return }
|
|
||||||
|
|
||||||
devices.remove(at: index)
|
|
||||||
devices.insert(device, at: index)
|
|
||||||
|
|
||||||
delegate?.deviceDidChange(device)
|
|
||||||
} else {
|
|
||||||
devices.append(device)
|
|
||||||
delegate?.deviceDidComeOnline(device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CastDeviceScanner: NetServiceDelegate {
|
|
||||||
|
|
||||||
public func netServiceDidResolveAddress(_ sender: NetService) {
|
|
||||||
guard let infoDict = sender.infoDict else {
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("No TXT record for \(sender), skipping")
|
|
||||||
#endif
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("Did resolve service: \(sender)")
|
|
||||||
NSLog("\(infoDict)")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
guard infoDict["id"] != nil else {
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("No id for device \(sender), skipping")
|
|
||||||
#endif
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addDevice(CastDevice(service: sender, info: infoDict))
|
|
||||||
}
|
|
||||||
|
|
||||||
public func netService(_ sender: NetService, didNotResolve errorDict: [String: NSNumber]) {
|
|
||||||
removeService(sender)
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
NSLog("!! Failed to resolve service: \(sender) - \(errorDict) !!")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NetService {
|
|
||||||
var infoDict: [String: String]? {
|
|
||||||
guard let data = txtRecordData() else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var dict = [String: String]()
|
|
||||||
NetService.dictionary(fromTXTRecord: data).forEach({ dict[$0.key] = String(data: $0.value, encoding: .utf8)! })
|
|
||||||
|
|
||||||
return dict
|
|
||||||
}
|
|
||||||
|
|
||||||
var id: String? {
|
|
||||||
return infoDict?["id"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public protocol CastDeviceScannerDelegate: AnyObject {
|
|
||||||
func deviceDidComeOnline(_ device: CastDevice)
|
|
||||||
func deviceDidChange(_ device: CastDevice)
|
|
||||||
func deviceDidGoOffline(_ device: CastDevice)
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
//
|
|
||||||
// CastChannel.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
open class CastChannel {
|
|
||||||
let namespace: String
|
|
||||||
weak var requestDispatcher: RequestDispatchable!
|
|
||||||
|
|
||||||
init(namespace: String) {
|
|
||||||
self.namespace = namespace
|
|
||||||
}
|
|
||||||
|
|
||||||
open func handleResponse(_ json: JSON, sourceId: String) {
|
|
||||||
// print(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
open func handleResponse(_ data: Data, sourceId: String) {
|
|
||||||
print("\n--Binary response--\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
public func send(_ request: CastRequest, response: CastResponseHandler? = nil) {
|
|
||||||
requestDispatcher.send(request, response: response)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
//
|
|
||||||
// Channelable.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
protocol Channelable: RequestDispatchable {
|
|
||||||
var channels: [String: CastChannel] { get set }
|
|
||||||
|
|
||||||
func add(channel: CastChannel)
|
|
||||||
func remove(channel: CastChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Channelable {
|
|
||||||
public func add(channel: CastChannel) {
|
|
||||||
let namespace = channel.namespace
|
|
||||||
guard channels[namespace] == nil else {
|
|
||||||
print("Channel already attached for \(namespace)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channels[namespace] = channel
|
|
||||||
channel.requestDispatcher = self
|
|
||||||
}
|
|
||||||
|
|
||||||
public func remove(channel: CastChannel) {
|
|
||||||
let namespace = channel.namespace
|
|
||||||
guard let channel = channels.removeValue(forKey: namespace) else {
|
|
||||||
print("No channel attached for \(namespace)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.requestDispatcher = nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
//
|
|
||||||
// DeviceAuthChannel.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class DeviceAuthChannel: CastChannel {
|
|
||||||
typealias CastAuthChallenge = Extensions_Api_CastChannel_AuthChallenge
|
|
||||||
typealias CastAuthMessage = Extensions_Api_CastChannel_DeviceAuthMessage
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func sendAuthChallenge() throws {
|
|
||||||
let message = CastAuthMessage.with {
|
|
||||||
$0.challenge = CastAuthChallenge()
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: try message.serializedData())
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
//
|
|
||||||
// DeviceConnectionChannel.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class DeviceConnectionChannel: CastChannel {
|
|
||||||
override weak var requestDispatcher: RequestDispatchable! {
|
|
||||||
didSet {
|
|
||||||
if let _ = requestDispatcher {
|
|
||||||
connect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect() {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.connect.rawValue])
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect(to app: CastApp) {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: app.transportId,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.connect.rawValue])
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func leave(_ app: CastApp) {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: app.transportId,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.close.rawValue])
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
//
|
|
||||||
// DeviceDiscoveryChannel.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class DeviceDiscoveryChannel: CastChannel {
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.discovery)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestDeviceInfo() {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.getDeviceInfo.rawValue])
|
|
||||||
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
print(json)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
//
|
|
||||||
// DeviceSetupChannel.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class DeviceSetupChannel: CastChannel {
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.setup)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestDeviceConfig() {
|
|
||||||
let params = [
|
|
||||||
"version",
|
|
||||||
"name",
|
|
||||||
"build_info.cast_build_revision",
|
|
||||||
"net.ip_address",
|
|
||||||
"net.online",
|
|
||||||
"net.ssid",
|
|
||||||
"wifi.signal_level",
|
|
||||||
"wifi.noise_level"
|
|
||||||
]
|
|
||||||
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.getDeviceConfig.rawValue,
|
|
||||||
"params": params,
|
|
||||||
"data": [:]
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
print(json)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestSetDeviceConfig() {
|
|
||||||
// let data: [String: Any] = [
|
|
||||||
// "name": "JUNK",
|
|
||||||
// "settings": [
|
|
||||||
//
|
|
||||||
// ]
|
|
||||||
// ]
|
|
||||||
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.getDeviceConfig.rawValue,
|
|
||||||
"data": [:]
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
print(json)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestAppDeviceId(app: CastApp) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.getAppDeviceId.rawValue,
|
|
||||||
"data": ["app_id": app.id]
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
print(json)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
//
|
|
||||||
// CastHeartbeatChannel.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
class HeartbeatChannel: CastChannel {
|
|
||||||
private lazy var timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(sendPing), userInfo: nil, repeats: true)
|
|
||||||
|
|
||||||
private let disconnectTimeout: TimeInterval = 10
|
|
||||||
private var disconnectTimer: Timer? {
|
|
||||||
willSet {
|
|
||||||
disconnectTimer?.invalidate()
|
|
||||||
}
|
|
||||||
didSet {
|
|
||||||
guard let timer = disconnectTimer else { return }
|
|
||||||
|
|
||||||
RunLoop.main.add(timer, forMode: RunLoop.Mode.common)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override weak var requestDispatcher: RequestDispatchable! {
|
|
||||||
didSet {
|
|
||||||
if let _ = requestDispatcher {
|
|
||||||
startBeating()
|
|
||||||
} else {
|
|
||||||
timer.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var delegate: HeartbeatChannelDelegate? {
|
|
||||||
return requestDispatcher as? HeartbeatChannelDelegate
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.heartbeat)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
|
||||||
delegate?.channelDidConnect(self)
|
|
||||||
|
|
||||||
guard let rawType = json["type"].string else { return }
|
|
||||||
|
|
||||||
guard let type = CastMessageType(rawValue: rawType) else {
|
|
||||||
print("Unknown type: \(rawType)")
|
|
||||||
print(json)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if type == .ping {
|
|
||||||
sendPong(to: sourceId)
|
|
||||||
print("PING from \(sourceId)")
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectTimer = Timer(timeInterval: disconnectTimeout,
|
|
||||||
target: self,
|
|
||||||
selector: #selector(handleTimeout),
|
|
||||||
userInfo: nil,
|
|
||||||
repeats: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startBeating() {
|
|
||||||
_ = timer
|
|
||||||
sendPing()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func sendPing() {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.transport,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.ping.rawValue])
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func sendPong(to destinationId: String) {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: destinationId,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.pong.rawValue])
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func handleTimeout() {
|
|
||||||
delegate?.channelDidTimeout(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol HeartbeatChannelDelegate: AnyObject {
|
|
||||||
func channelDidConnect(_ channel: HeartbeatChannel)
|
|
||||||
func channelDidTimeout(_ channel: HeartbeatChannel)
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
//
|
|
||||||
// MediaControlChannel.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Result
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
class MediaControlChannel: CastChannel {
|
|
||||||
private var delegate: MediaControlChannelDelegate? {
|
|
||||||
return requestDispatcher as? MediaControlChannelDelegate
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.media)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
|
||||||
guard let rawType = json["type"].string else { return }
|
|
||||||
|
|
||||||
guard let type = CastMessageType(rawValue: rawType) else {
|
|
||||||
print("Unknown type: \(rawType)")
|
|
||||||
print(json)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .mediaStatus:
|
|
||||||
guard let status = json["status"].array?.first else { return }
|
|
||||||
|
|
||||||
delegate?.channel(self, didReceive: CastMediaStatus(json: status))
|
|
||||||
|
|
||||||
default:
|
|
||||||
print(rawType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestMediaStatus(for app: CastApp, completion: ((Result<CastMediaStatus, CastError>) -> Void)? = nil) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue,
|
|
||||||
CastJSONPayloadKeys.sessionId: app.sessionId
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: app.transportId,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
if let completion = completion {
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
completion(Result(value: CastMediaStatus(json: json)))
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
completion(Result(error: error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func sendPause(for app: CastApp, mediaSessionId: Int) {
|
|
||||||
send(.pause, for: app, mediaSessionId: mediaSessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func sendPlay(for app: CastApp, mediaSessionId: Int) {
|
|
||||||
send(.play, for: app, mediaSessionId: mediaSessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func sendStop(for app: CastApp, mediaSessionId: Int) {
|
|
||||||
send(.stop, for: app, mediaSessionId: mediaSessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func sendSeek(to currentTime: Float, for app: CastApp, mediaSessionId: Int) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.seek.rawValue,
|
|
||||||
CastJSONPayloadKeys.sessionId: app.sessionId,
|
|
||||||
CastJSONPayloadKeys.currentTime: currentTime,
|
|
||||||
CastJSONPayloadKeys.mediaSessionId: mediaSessionId
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: app.transportId,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func send(_ message: CastMessageType, for app: CastApp, mediaSessionId: Int) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: message.rawValue,
|
|
||||||
CastJSONPayloadKeys.mediaSessionId: mediaSessionId
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: app.transportId,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func load(media: CastMedia, with app: CastApp, completion: @escaping (Result<CastMediaStatus, CastError>) -> Void) {
|
|
||||||
var payload = media.dict
|
|
||||||
payload[CastJSONPayloadKeys.type] = CastMessageType.load.rawValue
|
|
||||||
payload[CastJSONPayloadKeys.sessionId] = app.sessionId
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: app.transportId,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
guard let status = json["status"].array?.first else { return }
|
|
||||||
|
|
||||||
completion(Result(value: CastMediaStatus(json: status)))
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
completion(Result(error: CastError.load(error.localizedDescription)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol MediaControlChannelDelegate: AnyObject {
|
|
||||||
func channel(_ channel: MediaControlChannel, didReceive mediaStatus: CastMediaStatus)
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
//
|
|
||||||
// MultizoneControlChannel.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Result
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
class MultizoneControlChannel: CastChannel {
|
|
||||||
override weak var requestDispatcher: RequestDispatchable! {
|
|
||||||
didSet {
|
|
||||||
if let _ = requestDispatcher {
|
|
||||||
requestStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var delegate: MultizoneControlChannelDelegate? {
|
|
||||||
return requestDispatcher as? MultizoneControlChannelDelegate
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.multizone)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
|
||||||
guard let rawType = json["type"].string else { return }
|
|
||||||
|
|
||||||
guard let type = CastMessageType(rawValue: rawType) else {
|
|
||||||
print("Unknown type: \(rawType)")
|
|
||||||
print(json)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .multizoneStatus:
|
|
||||||
delegate?.channel(self, didReceive: CastMultizoneStatus(json: json))
|
|
||||||
|
|
||||||
case .deviceAdded:
|
|
||||||
let device = CastMultizoneDevice(json: json[CastJSONPayloadKeys.device])
|
|
||||||
delegate?.channel(self, added: device)
|
|
||||||
|
|
||||||
case .deviceUpdated:
|
|
||||||
let device = CastMultizoneDevice(json: json[CastJSONPayloadKeys.device])
|
|
||||||
delegate?.channel(self, updated: device)
|
|
||||||
|
|
||||||
case .deviceRemoved:
|
|
||||||
guard let deviceId = json[CastJSONPayloadKeys.deviceId].string else { return }
|
|
||||||
delegate?.channel(self, removed: deviceId)
|
|
||||||
|
|
||||||
default:
|
|
||||||
print(rawType)
|
|
||||||
print(json)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestStatus(completion: ((Result<CastStatus, CastError>) -> Void)? = nil) {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue])
|
|
||||||
|
|
||||||
if let completion = completion {
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
completion(Result(value: CastStatus(json: json)))
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
completion(Result(error: error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setVolume(_ volume: Float, for device: CastMultizoneDevice) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setDeviceVolume.rawValue,
|
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.level: volume],
|
|
||||||
CastJSONPayloadKeys.deviceId: device.id
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setMuted(_ isMuted: Bool, for device: CastMultizoneDevice) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.muted: isMuted],
|
|
||||||
CastJSONPayloadKeys.deviceId: device.id
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol MultizoneControlChannelDelegate: AnyObject {
|
|
||||||
func channel(_ channel: MultizoneControlChannel, didReceive status: CastMultizoneStatus)
|
|
||||||
func channel(_ channel: MultizoneControlChannel, added device: CastMultizoneDevice)
|
|
||||||
func channel(_ channel: MultizoneControlChannel, updated device: CastMultizoneDevice)
|
|
||||||
func channel(_ channel: MultizoneControlChannel, removed deviceId: String)
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
//
|
|
||||||
// ReceiverControlChannel.swift
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Result
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
class ReceiverControlChannel: CastChannel {
|
|
||||||
override weak var requestDispatcher: RequestDispatchable! {
|
|
||||||
didSet {
|
|
||||||
if let _ = requestDispatcher {
|
|
||||||
requestStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var delegate: ReceiverControlChannelDelegate? {
|
|
||||||
return requestDispatcher as? ReceiverControlChannelDelegate
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(namespace: CastNamespace.receiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
|
||||||
guard let rawType = json["type"].string else { return }
|
|
||||||
|
|
||||||
guard let type = CastMessageType(rawValue: rawType) else {
|
|
||||||
print("Unknown type: \(rawType)")
|
|
||||||
print(json)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .status:
|
|
||||||
delegate?.channel(self, didReceive: CastStatus(json: json))
|
|
||||||
|
|
||||||
default:
|
|
||||||
print(rawType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func getAppAvailability(apps: [CastApp], completion: @escaping (Result<AppAvailability, CastError>) -> Void) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.availableApps.rawValue,
|
|
||||||
CastJSONPayloadKeys.appId: apps.map { $0.id }
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
completion(Result(value: AppAvailability(json: json)))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(Result(error: CastError.launch(error.localizedDescription)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestStatus(completion: ((Result<CastStatus, CastError>) -> Void)? = nil) {
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue])
|
|
||||||
|
|
||||||
if let completion = completion {
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
completion(Result(value: CastStatus(json: json)))
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
completion(Result(error: error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func launch(appId: String, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.launch.rawValue,
|
|
||||||
CastJSONPayloadKeys.appId: appId
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let json):
|
|
||||||
guard let app = CastStatus(json: json).apps.first else {
|
|
||||||
completion(Result(error: CastError.launch("Unable to get launched app instance")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(Result(value: app))
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
completion(Result(error: error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func stop(app: CastApp) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.stop.rawValue,
|
|
||||||
CastJSONPayloadKeys.sessionId: app.sessionId
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setVolume(_ volume: Float) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.level: volume]
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setMuted(_ isMuted: Bool) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.muted: isMuted]
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
|
||||||
destinationId: CastConstants.receiver,
|
|
||||||
payload: payload)
|
|
||||||
|
|
||||||
send(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol ReceiverControlChannelDelegate: RequestDispatchable {
|
|
||||||
func channel(_ channel: ReceiverControlChannel, didReceive status: CastStatus)
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
//
|
|
||||||
// RequestDispatchable.swift
|
|
||||||
// OpenCastSwift Mac
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
protocol RequestDispatchable: AnyObject {
|
|
||||||
func nextRequestId() -> Int
|
|
||||||
|
|
||||||
func request(withNamespace namespace: String, destinationId: String, payload: [String: Any]) -> CastRequest
|
|
||||||
func request(withNamespace namespace: String, destinationId: String, payload: Data) -> CastRequest
|
|
||||||
|
|
||||||
func send(_ request: CastRequest, response: CastResponseHandler?)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RequestDispatchable {
|
|
||||||
func request(withNamespace namespace: String, destinationId: String, payload: [String: Any]) -> CastRequest {
|
|
||||||
var payload = payload
|
|
||||||
let requestId = nextRequestId()
|
|
||||||
payload[CastJSONPayloadKeys.requestId] = requestId
|
|
||||||
|
|
||||||
return CastRequest(id: requestId,
|
|
||||||
namespace: namespace,
|
|
||||||
destinationId: destinationId,
|
|
||||||
payload: payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func request(withNamespace namespace: String, destinationId: String, payload: Data) -> CastRequest {
|
|
||||||
return CastRequest(id: nextRequestId(),
|
|
||||||
namespace: namespace,
|
|
||||||
destinationId: destinationId,
|
|
||||||
payload: payload)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
//
|
|
||||||
// OpenCastSwift.h
|
|
||||||
// OpenCastSwift
|
|
||||||
//
|
|
||||||
// Created by Miles Hollingsworth on 4/22/18
|
|
||||||
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
@import Foundation;
|
|
||||||
|
|
||||||
FOUNDATION_EXPORT double ChromeCastCoreVersionNumber;
|
|
||||||
FOUNDATION_EXPORT const unsigned char ChromeCastCoreVersionString[];
|
|
|
@ -17,30 +17,6 @@
|
||||||
<view key="view" autoresizesSubviews="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IQg-r0-AeH">
|
<view key="view" autoresizesSubviews="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IQg-r0-AeH">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
|
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view autoresizesSubviews="NO" tag="1" contentMode="scaleToFill" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fa2-nx-Dzx" userLabel="VideoCastingContentView">
|
|
||||||
<rect key="frame" x="31" y="0.0" width="834" height="414"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="Streaming to Chromecast" textAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="CJ6-bw-cyX">
|
|
||||||
<rect key="frame" x="224" y="105" width="387" height="128"/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="128" id="W6f-zI-T4b"/>
|
|
||||||
</constraints>
|
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="28"/>
|
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
|
||||||
</textView>
|
|
||||||
</subviews>
|
|
||||||
<viewLayoutGuide key="safeArea" id="by1-Hb-CaQ"/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<gestureRecognizers/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="CJ6-bw-cyX" firstAttribute="leading" secondItem="by1-Hb-CaQ" secondAttribute="leading" constant="211" id="7lb-qN-MqL"/>
|
|
||||||
<constraint firstItem="by1-Hb-CaQ" firstAttribute="trailing" secondItem="CJ6-bw-cyX" secondAttribute="trailing" constant="210" id="lKv-tL-HF2"/>
|
|
||||||
<constraint firstItem="CJ6-bw-cyX" firstAttribute="top" secondItem="by1-Hb-CaQ" secondAttribute="top" constant="105" id="xTG-rr-kLR"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<view autoresizesSubviews="NO" tag="1" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tsh-rC-BwO" userLabel="VideoContentView">
|
<view autoresizesSubviews="NO" tag="1" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tsh-rC-BwO" userLabel="VideoContentView">
|
||||||
<rect key="frame" x="31" y="0.0" width="834" height="414"/>
|
<rect key="frame" x="31" y="0.0" width="834" height="414"/>
|
||||||
<viewLayoutGuide key="safeArea" id="aVY-BC-PZU"/>
|
<viewLayoutGuide key="safeArea" id="aVY-BC-PZU"/>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import MobileVLCKit
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
import Combine
|
import Combine
|
||||||
|
import GoogleCast
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
struct Subtitle {
|
struct Subtitle {
|
||||||
|
@ -41,7 +42,7 @@ protocol PlayerViewControllerDelegate: AnyObject {
|
||||||
func exitPlayer(_ viewController: PlayerViewController)
|
func exitPlayer(_ viewController: PlayerViewController)
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDelegate, CastClientDelegate {
|
class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRemoteMediaClientListener {
|
||||||
|
|
||||||
weak var delegate: PlayerViewControllerDelegate?
|
weak var delegate: PlayerViewControllerDelegate?
|
||||||
|
|
||||||
|
@ -69,33 +70,28 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
var startTime: Int = 0
|
var startTime: Int = 0
|
||||||
var controlsAppearTime: Double = 0
|
var controlsAppearTime: Double = 0
|
||||||
|
|
||||||
var discoveredCastDevices: [CastDevice] = [] //not private due to VPCDS using it.
|
var playerDestination: PlayerDestination = .local;
|
||||||
var selectedCastDevice: CastDevice? //same here
|
var discoveredCastDevices: [GCKDevice] = [];
|
||||||
private var castClient: CastClient?
|
var selectedCastDevice: GCKDevice?;
|
||||||
private var playerDestination: PlayerDestination = .local;
|
var jellyfinCastChannel: GCKGenericChannel?
|
||||||
private var castAppTransportID: String = "";
|
var remotePositionTicks: Int = 0
|
||||||
private var remotePlayIsPlaying: Bool = false;
|
private var castDiscoveryManager: GCKDiscoveryManager {
|
||||||
private var remotePlaySeekState: Int = 0;
|
return GCKCastContext.sharedInstance().discoveryManager
|
||||||
private let castScanner: CastDeviceScanner = CastDeviceScanner();
|
}
|
||||||
|
private var castSessionManager: GCKSessionManager {
|
||||||
|
return GCKCastContext.sharedInstance().sessionManager
|
||||||
|
}
|
||||||
|
|
||||||
var selectedAudioTrack: Int32 = -1 {
|
var selectedAudioTrack: Int32 = -1
|
||||||
didSet {
|
var selectedCaptionTrack: Int32 = -1
|
||||||
print(selectedAudioTrack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var selectedCaptionTrack: Int32 = -1 {
|
|
||||||
didSet {
|
|
||||||
print(selectedCaptionTrack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var playSessionId: String = ""
|
var playSessionId: String = ""
|
||||||
var lastProgressReportTime: Double = 0
|
var lastProgressReportTime: Double = 0
|
||||||
|
|
||||||
var subtitleTrackArray: [Subtitle] = []
|
var subtitleTrackArray: [Subtitle] = []
|
||||||
var audioTrackArray: [AudioTrack] = []
|
var audioTrackArray: [AudioTrack] = []
|
||||||
|
|
||||||
var manifest: BaseItemDto = BaseItemDto()
|
var manifest: BaseItemDto = BaseItemDto()
|
||||||
var playbackItem = PlaybackItem()
|
var playbackItem = PlaybackItem()
|
||||||
|
|
||||||
|
|
||||||
// MARK: IBActions
|
// MARK: IBActions
|
||||||
@IBAction func seekSliderStart(_ sender: Any) {
|
@IBAction func seekSliderStart(_ sender: Any) {
|
||||||
|
@ -106,7 +102,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func seekSliderValueChanged(_ sender: Any) {
|
@IBAction func seekSliderValueChanged(_ sender: Any) {
|
||||||
let videoDuration = Double(mediaPlayer.time.intValue + abs(mediaPlayer.remainingTime.intValue))/1000
|
let videoDuration: Double = Double(manifest.runTimeTicks! / Int64(10_000_000))
|
||||||
let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration)
|
let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration)
|
||||||
let scrubRemaining = videoDuration - secondsScrubbedTo
|
let scrubRemaining = videoDuration - secondsScrubbedTo
|
||||||
let remainingTime = scrubRemaining
|
let remainingTime = scrubRemaining
|
||||||
|
@ -121,21 +117,31 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func seekSliderEnd(_ sender: Any) {
|
@IBAction func seekSliderEnd(_ sender: Any) {
|
||||||
print("ss end")
|
let videoPosition = playerDestination == .local ? Double(mediaPlayer.time.intValue / 1000) : Double(remotePositionTicks / Int(10_000_000))
|
||||||
let videoPosition = Double(mediaPlayer.time.intValue)
|
let videoDuration = Double(manifest.runTimeTicks! / Int64(10_000_000))
|
||||||
let videoDuration = Double(mediaPlayer.time.intValue + abs(mediaPlayer.remainingTime.intValue))
|
|
||||||
// Scrub is value from 0..1 - find position in video and add / or remove.
|
// Scrub is value from 0..1 - find position in video and add / or remove.
|
||||||
let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration)
|
let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration)
|
||||||
let offset = secondsScrubbedTo - videoPosition
|
let offset = secondsScrubbedTo - videoPosition
|
||||||
|
|
||||||
|
print(videoPosition)
|
||||||
|
print(videoDuration)
|
||||||
|
print(secondsScrubbedTo)
|
||||||
|
print(offset)
|
||||||
|
|
||||||
if(playerDestination == .local) {
|
if(playerDestination == .local) {
|
||||||
mediaPlayer.play()
|
|
||||||
if offset > 0 {
|
if offset > 0 {
|
||||||
mediaPlayer.jumpForward(Int32(offset)/1000)
|
mediaPlayer.jumpForward(Int32(offset))
|
||||||
} else {
|
} else {
|
||||||
mediaPlayer.jumpBackward(Int32(abs(offset))/1000)
|
mediaPlayer.jumpBackward(Int32(abs(offset)))
|
||||||
}
|
}
|
||||||
|
mediaPlayer.play()
|
||||||
sendProgressReport(eventName: "unpause")
|
sendProgressReport(eventName: "unpause")
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
sendJellyfinCommand(command: "Seek", options: [
|
||||||
|
"position": secondsScrubbedTo
|
||||||
|
])
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,12 +149,8 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
sendStopReport()
|
sendStopReport()
|
||||||
mediaPlayer.stop()
|
mediaPlayer.stop()
|
||||||
|
|
||||||
if(playerDestination == .remote) {
|
if(castSessionManager.hasConnectedCastSession()) {
|
||||||
castClient?.stopCurrentApp()
|
castSessionManager.endSessionAndStopCasting(true)
|
||||||
castClient?.disconnect()
|
|
||||||
castClient = nil
|
|
||||||
selectedCastDevice = nil
|
|
||||||
playerDestination = .local
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate?.exitPlayer(self)
|
delegate?.exitPlayer(self)
|
||||||
|
@ -194,7 +196,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||||
paused = false
|
paused = false
|
||||||
} else {
|
} else {
|
||||||
sendCastCommand(cmd: "Unpause")
|
sendJellyfinCommand(command: "Unpause", options: [:])
|
||||||
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||||
paused = false
|
paused = false
|
||||||
}
|
}
|
||||||
|
@ -204,7 +206,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
||||||
paused = true
|
paused = true
|
||||||
} else {
|
} else {
|
||||||
sendCastCommand(cmd: "Pause")
|
sendJellyfinCommand(command: "Pause", options: [:])
|
||||||
mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
||||||
paused = true
|
paused = true
|
||||||
}
|
}
|
||||||
|
@ -226,7 +228,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: Cast start
|
//MARK: Cast methods
|
||||||
@IBAction func castButtonPressed(_ sender: Any) {
|
@IBAction func castButtonPressed(_ sender: Any) {
|
||||||
if(selectedCastDevice == nil) {
|
if(selectedCastDevice == nil) {
|
||||||
castDeviceVC = VideoPlayerCastDeviceSelectorView()
|
castDeviceVC = VideoPlayerCastDeviceSelectorView()
|
||||||
|
@ -242,115 +244,31 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
castClient?.stopCurrentApp()
|
castSessionManager.endSessionAndStopCasting(true)
|
||||||
castClient?.disconnect()
|
|
||||||
selectedCastDevice = nil;
|
selectedCastDevice = nil;
|
||||||
castClient = nil;
|
|
||||||
self.castButton.isEnabled = true
|
self.castButton.isEnabled = true
|
||||||
self.castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
self.castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||||
|
playerDestination = .local
|
||||||
//disconnect cast device.
|
startLocalPlaybackEngine()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func castPopoverDismissed() {
|
func castPopoverDismissed() {
|
||||||
castDeviceVC?.dismiss(animated: true, completion: nil)
|
castDeviceVC?.dismiss(animated: true, completion: nil)
|
||||||
self.mediaPlayer.play()
|
if(playerDestination == .local) {
|
||||||
|
self.mediaPlayer.play()
|
||||||
|
}
|
||||||
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func castDeviceChanged() {
|
func castDeviceChanged() {
|
||||||
if(selectedCastDevice != nil) {
|
if(selectedCastDevice != nil) {
|
||||||
castClient = CastClient(device: selectedCastDevice!)
|
playerDestination = .remote
|
||||||
castClient!.delegate = self
|
castSessionManager.add(self)
|
||||||
castClient!.connect()
|
castSessionManager.startSession(with: selectedCastDevice!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendCastCommand(cmd: String) {
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
"options": [],
|
|
||||||
"command": cmd,
|
|
||||||
"userId": SessionManager.current.user.user_id!,
|
|
||||||
"deviceId": SessionManager.current.deviceID,
|
|
||||||
"accessToken": SessionManager.current.accessToken,
|
|
||||||
"serverAddress": ServerEnvironment.current.server.baseURI!,
|
|
||||||
"serverId": ServerEnvironment.current.server.server_id!,
|
|
||||||
"serverVersion": "10.8.0",
|
|
||||||
"receiverName": self.selectedCastDevice!.name
|
|
||||||
]
|
|
||||||
let req = CastRequest(id: castClient!.nextRequestId(), namespace: "urn:x-cast:com.connectsdk", destinationId: castAppTransportID, payload: payload)
|
|
||||||
castClient!.send(req, response: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func castClient(_ client: CastClient, connectionTo device: CastDevice, didFailWith error: Error?) {
|
|
||||||
dump(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func castClient(_ client: CastClient, willConnectTo device: CastDevice) {
|
|
||||||
print("Connecting")
|
|
||||||
mediaPlayer.pause()
|
|
||||||
castScanner.stopScanning()
|
|
||||||
self.castButton.setImage(UIImage(named: "CastConnecting1"), for: .normal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func castClient(_ client: CastClient, didConnectTo device: CastDevice) {
|
|
||||||
print("Connected")
|
|
||||||
self.castButton.setImage(UIImage(named: "CastConnected"), for: .normal)
|
|
||||||
|
|
||||||
//Launch player
|
|
||||||
client.launch(appId: "F007D354") { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let app):
|
|
||||||
// here you would probably call client.load() to load some media
|
|
||||||
let payload: [String: Any] = [
|
|
||||||
"options": [
|
|
||||||
"items": [[
|
|
||||||
"Id": self.manifest.id!,
|
|
||||||
"ServerId": ServerEnvironment.current.server.server_id!,
|
|
||||||
"Name": self.manifest.name!,
|
|
||||||
"Type": self.manifest.type!,
|
|
||||||
"MediaType": self.manifest.mediaType!,
|
|
||||||
"IsFolder": self.manifest.isFolder!
|
|
||||||
]]
|
|
||||||
],
|
|
||||||
"command": "PlayNow",
|
|
||||||
"userId": SessionManager.current.user.user_id!,
|
|
||||||
"deviceId": SessionManager.current.deviceID,
|
|
||||||
"accessToken": SessionManager.current.accessToken,
|
|
||||||
"serverAddress": ServerEnvironment.current.server.baseURI!,
|
|
||||||
"serverId": ServerEnvironment.current.server.server_id!,
|
|
||||||
"serverVersion": "10.8.0",
|
|
||||||
"receiverName": self.selectedCastDevice!.name,
|
|
||||||
"subtitleBurnIn": false
|
|
||||||
]
|
|
||||||
self.castAppTransportID = app.transportId
|
|
||||||
let req = CastRequest(id: client.nextRequestId(), namespace: "urn:x-cast:com.connectsdk", destinationId: app.transportId, payload: payload)
|
|
||||||
client.send(req, response: self.castResponseHandler)
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Hide VLC player
|
|
||||||
videoContentView.isHidden = true;
|
|
||||||
playerDestination = .remote;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func castClient(_ client: CastClient, didDisconnectFrom device: CastDevice) {
|
|
||||||
print("Disconnected")
|
|
||||||
castScanner.startScanning()
|
|
||||||
playerDestination = .local;
|
|
||||||
videoContentView.isHidden = false;
|
|
||||||
self.castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func castResponseHandler(result: Result<JSON, CastError>) {
|
|
||||||
dump(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: Cast End
|
//MARK: Cast End
|
||||||
func settingsPopoverDismissed() {
|
func settingsPopoverDismissed() {
|
||||||
optionsVC?.dismiss(animated: true, completion: nil)
|
optionsVC?.dismiss(animated: true, completion: nil)
|
||||||
|
@ -382,7 +300,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
self.mediaPlayer.pause()
|
self.mediaPlayer.pause()
|
||||||
self.sendProgressReport(eventName: "pause")
|
self.sendProgressReport(eventName: "pause")
|
||||||
} else {
|
} else {
|
||||||
self.sendCastCommand(cmd: "Pause")
|
self.sendJellyfinCommand(command: "Pause", options: [:])
|
||||||
}
|
}
|
||||||
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
|
||||||
return .success
|
return .success
|
||||||
|
@ -394,7 +312,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
self.mediaPlayer.play()
|
self.mediaPlayer.play()
|
||||||
self.sendProgressReport(eventName: "unpause")
|
self.sendProgressReport(eventName: "unpause")
|
||||||
} else {
|
} else {
|
||||||
self.sendCastCommand(cmd: "Unpause")
|
self.sendJellyfinCommand(command: "Unpause", options: [:])
|
||||||
}
|
}
|
||||||
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||||
return .success
|
return .success
|
||||||
|
@ -475,20 +393,37 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaHasStartedPlaying() {
|
func mediaHasStartedPlaying() {
|
||||||
NotificationCenter.default.addObserver(forName: CastDeviceScanner.deviceListDidChange, object: castScanner, queue: nil) { _ in
|
castButton.isHidden = true;
|
||||||
self.discoveredCastDevices = self.castScanner.devices
|
let discoveryCriteria = GCKDiscoveryCriteria(applicationID: "F007D354")
|
||||||
if !self.castScanner.devices.isEmpty {
|
let gckCastOptions = GCKCastOptions(discoveryCriteria: discoveryCriteria)
|
||||||
self.castButton.isEnabled = true
|
GCKCastContext.setSharedInstanceWith(gckCastOptions)
|
||||||
self.castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
castDiscoveryManager.passiveScan = true
|
||||||
} else {
|
castDiscoveryManager.add(self)
|
||||||
self.castButton.isEnabled = false
|
castDiscoveryManager.startDiscovery()
|
||||||
self.castButton.setImage(nil, for: .normal)
|
}
|
||||||
|
|
||||||
|
func didUpdateDeviceList() {
|
||||||
|
let totalDevices = castDiscoveryManager.deviceCount;
|
||||||
|
discoveredCastDevices = []
|
||||||
|
if(totalDevices > 0) {
|
||||||
|
for i in 0...totalDevices-1 {
|
||||||
|
let device = castDiscoveryManager.device(at: i)
|
||||||
|
discoveredCastDevices.append(device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castScanner.startScanning()
|
if !discoveredCastDevices.isEmpty {
|
||||||
|
castButton.isHidden = false
|
||||||
|
castButton.isEnabled = true
|
||||||
|
castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||||
|
} else {
|
||||||
|
castButton.isHidden = true
|
||||||
|
castButton.isEnabled = false
|
||||||
|
castButton.setImage(nil, for: .normal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: viewDidAppear
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
overrideUserInterfaceStyle = .dark
|
overrideUserInterfaceStyle = .dark
|
||||||
|
@ -606,51 +541,53 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
selectedAudioTrack = audioTrackArray[0].id
|
selectedAudioTrack = audioTrackArray[0].id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("gotToEnd")
|
|
||||||
|
|
||||||
self.sendPlayReport()
|
self.sendPlayReport()
|
||||||
playbackItem = item
|
playbackItem = item
|
||||||
}
|
}
|
||||||
|
startLocalPlaybackEngine()
|
||||||
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
|
|
||||||
mediaPlayer.play()
|
|
||||||
|
|
||||||
// 1 second = 10,000,000 ticks
|
|
||||||
|
|
||||||
let rawStartTicks = manifest.userData?.playbackPositionTicks ?? 0
|
|
||||||
|
|
||||||
if rawStartTicks != 0 {
|
|
||||||
let startSeconds = rawStartTicks / 10_000_000
|
|
||||||
mediaPlayer.jumpForward(Int32(startSeconds))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause and load captions into memory.
|
|
||||||
mediaPlayer.pause()
|
|
||||||
|
|
||||||
var shouldHaveSubtitleTracks = 0
|
|
||||||
subtitleTrackArray.forEach { sub in
|
|
||||||
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" {
|
|
||||||
shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1
|
|
||||||
mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for captions to load
|
|
||||||
delegate?.showLoadingView(self)
|
|
||||||
|
|
||||||
while mediaPlayer.numberOfSubtitlesTracks != shouldHaveSubtitleTracks {}
|
|
||||||
|
|
||||||
// Select default track & resume playback
|
|
||||||
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
|
|
||||||
mediaPlayer.pause()
|
|
||||||
mediaPlayer.play()
|
|
||||||
self.mediaHasStartedPlaying()
|
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startLocalPlaybackEngine() {
|
||||||
|
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
|
||||||
|
mediaPlayer.play()
|
||||||
|
|
||||||
|
// 1 second = 10,000,000 ticks
|
||||||
|
|
||||||
|
let rawStartTicks = manifest.userData?.playbackPositionTicks ?? 0
|
||||||
|
|
||||||
|
if rawStartTicks != 0 {
|
||||||
|
let startSeconds = rawStartTicks / 10_000_000
|
||||||
|
mediaPlayer.jumpForward(Int32(startSeconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause and load captions into memory.
|
||||||
|
mediaPlayer.pause()
|
||||||
|
|
||||||
|
var shouldHaveSubtitleTracks = 0
|
||||||
|
subtitleTrackArray.forEach { sub in
|
||||||
|
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" {
|
||||||
|
shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1
|
||||||
|
mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for captions to load
|
||||||
|
delegate?.showLoadingView(self)
|
||||||
|
|
||||||
|
while mediaPlayer.numberOfSubtitlesTracks != shouldHaveSubtitleTracks {}
|
||||||
|
|
||||||
|
// Select default track & resume playback
|
||||||
|
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
|
||||||
|
mediaPlayer.pause()
|
||||||
|
mediaPlayer.play()
|
||||||
|
self.mediaHasStartedPlaying()
|
||||||
|
delegate?.hideLoadingView(self)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: VideoPlayerSettings Delegate
|
// MARK: VideoPlayerSettings Delegate
|
||||||
func subtitleTrackChanged(newTrackID: Int32) {
|
func subtitleTrackChanged(newTrackID: Int32) {
|
||||||
selectedCaptionTrack = newTrackID
|
selectedCaptionTrack = newTrackID
|
||||||
|
@ -661,8 +598,144 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
selectedAudioTrack = newTrackID
|
selectedAudioTrack = newTrackID
|
||||||
mediaPlayer.currentAudioTrackIndex = newTrackID
|
mediaPlayer.currentAudioTrackIndex = newTrackID
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: VLCMediaPlayer Delegates
|
//MARK: - GCKGenericChannelDelegate
|
||||||
|
extension PlayerViewController: GCKGenericChannelDelegate {
|
||||||
|
func cast(_ channel: GCKGenericChannel, didReceiveTextMessage message: String, withNamespace protocolNamespace: String) {
|
||||||
|
print("~~~~~~~~~~~~~~~~~")
|
||||||
|
print(message)
|
||||||
|
print("~~~~~~~~~~~~~~~~~")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
if let data = message.data(using: .utf8) {
|
||||||
|
if let json = try? JSON(data: data) {
|
||||||
|
let messageType = json["type"].string ?? ""
|
||||||
|
if(messageType == "playbackprogress") {
|
||||||
|
print("Playback progress received from Cast!");
|
||||||
|
|
||||||
|
self.remotePositionTicks = json["data"]["PlayState"]["PositionTicks"].int ?? 0;
|
||||||
|
print(remotePositionTicks)
|
||||||
|
|
||||||
|
let remainingTime = (manifest.runTimeTicks! - Int64(remotePositionTicks))/10_000_000
|
||||||
|
print(remainingTime)
|
||||||
|
let hours = remainingTime / 3600
|
||||||
|
let minutes = (remainingTime % 3600) / 60
|
||||||
|
let seconds = (remainingTime % 3600) % 60
|
||||||
|
var timeTextStr = ""
|
||||||
|
if hours != 0 {
|
||||||
|
timeTextStr = "\(Int(hours)):\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"
|
||||||
|
} else {
|
||||||
|
timeTextStr = "\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"
|
||||||
|
}
|
||||||
|
timeText.text = timeTextStr
|
||||||
|
|
||||||
|
let playbackProgress = Int64(remotePositionTicks) / manifest.runTimeTicks!
|
||||||
|
seekSlider.setValue(Float(playbackProgress), animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendJellyfinCommand(command: String, options: [String: Any]) {
|
||||||
|
let payload: [String: Any] = [
|
||||||
|
"options": options,
|
||||||
|
"command": command,
|
||||||
|
"userId": SessionManager.current.user.user_id!,
|
||||||
|
"deviceId": SessionManager.current.deviceID,
|
||||||
|
"accessToken": SessionManager.current.accessToken,
|
||||||
|
"serverAddress": ServerEnvironment.current.server.baseURI!,
|
||||||
|
"serverId": ServerEnvironment.current.server.server_id!,
|
||||||
|
"serverVersion": "10.8.0",
|
||||||
|
"receiverName": castSessionManager.currentCastSession!.device.friendlyName!,
|
||||||
|
"subtitleBurnIn": false
|
||||||
|
]
|
||||||
|
let jsonData = JSON(payload)
|
||||||
|
|
||||||
|
jellyfinCastChannel?.sendTextMessage(jsonData.rawString()!, error: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - GCKSessionManagerListener
|
||||||
|
extension PlayerViewController: GCKSessionManagerListener {
|
||||||
|
func sessionDidStart(manager: GCKSessionManager, didStart session: GCKCastSession) {
|
||||||
|
self.sendStopReport()
|
||||||
|
mediaPlayer.stop()
|
||||||
|
|
||||||
|
playerDestination = .remote
|
||||||
|
videoContentView.isHidden = true;
|
||||||
|
videoControlsView.isHidden = false;
|
||||||
|
castButton.setImage(UIImage(named: "CastConnected"), for: .normal)
|
||||||
|
manager.currentCastSession?.start()
|
||||||
|
|
||||||
|
jellyfinCastChannel!.delegate = self
|
||||||
|
session.add(jellyfinCastChannel!)
|
||||||
|
|
||||||
|
if let client = session.remoteMediaClient {
|
||||||
|
client.add(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
let playNowOptions: [String: Any] = [
|
||||||
|
"items": [[
|
||||||
|
"Id": self.manifest.id!,
|
||||||
|
"ServerId": ServerEnvironment.current.server.server_id!,
|
||||||
|
"Name": self.manifest.name!,
|
||||||
|
"Type": self.manifest.type!,
|
||||||
|
"MediaType": self.manifest.mediaType!,
|
||||||
|
"IsFolder": self.manifest.isFolder!
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
sendJellyfinCommand(command: "PlayNow", options: playNowOptions)
|
||||||
|
sendJellyfinCommand(command: "Seek", options: [
|
||||||
|
"position": (manifest.runTimeTicks! - (Int64(mediaPlayer.remainingTime.intValue) * 10000)) / 10_000_000
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKCastSession) {
|
||||||
|
print("starting session")
|
||||||
|
self.jellyfinCastChannel = GCKGenericChannel(namespace: "urn:x-cast:com.connectsdk")
|
||||||
|
self.sessionDidStart(manager: sessionManager, didStart: session)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionManager(_ sessionManager: GCKSessionManager, didResumeCastSession session: GCKCastSession) {
|
||||||
|
self.jellyfinCastChannel = GCKGenericChannel(namespace: "urn:x-cast:com.connectsdk")
|
||||||
|
print("resuming session")
|
||||||
|
self.sessionDidStart(manager: sessionManager, didStart: session)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionManager(_ sessionManager: GCKSessionManager, didFailToStart session: GCKCastSession, withError error: Error) {
|
||||||
|
dump(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKCastSession, withError error: Error?) {
|
||||||
|
print("didEnd")
|
||||||
|
playerDestination = .local;
|
||||||
|
videoContentView.isHidden = false;
|
||||||
|
castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||||
|
startLocalPlaybackEngine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionManager(_ sessionManager: GCKSessionManager, didSuspend session: GCKCastSession, with reason: GCKConnectionSuspendReason) {
|
||||||
|
print("didSuspend")
|
||||||
|
playerDestination = .local;
|
||||||
|
videoContentView.isHidden = false;
|
||||||
|
castButton.setImage(UIImage(named: "CastDisconnected"), for: .normal)
|
||||||
|
startLocalPlaybackEngine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionManager(_ sessionManager: GCKSessionManager, castSession session: GCKCastSession, didReceiveDeviceStatus statusText: String?) {
|
||||||
|
print("96")
|
||||||
|
dump(statusText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionManager(_ sessionManager: GCKSessionManager, castSession session: GCKCastSession, didReceiveDeviceVolume volume: Float, muted: Bool) {
|
||||||
|
print("100")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - VLCMediaPlayer Delegates
|
||||||
|
extension PlayerViewController: VLCMediaPlayerDelegate {
|
||||||
func mediaPlayerStateChanged(_ aNotification: Notification!) {
|
func mediaPlayerStateChanged(_ aNotification: Notification!) {
|
||||||
let currentState: VLCMediaPlayerState = mediaPlayer.state
|
let currentState: VLCMediaPlayerState = mediaPlayer.state
|
||||||
switch currentState {
|
switch currentState {
|
||||||
|
@ -690,7 +763,8 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
mediaPlayer.pause()
|
mediaPlayer.pause()
|
||||||
usleep(10000)
|
usleep(10000)
|
||||||
mediaPlayer.play()
|
mediaPlayer.play()
|
||||||
|
delegate?.hideLoadingView(self)
|
||||||
|
paused = false
|
||||||
case .error :
|
case .error :
|
||||||
print("Video has error)")
|
print("Video has error)")
|
||||||
sendStopReport()
|
sendStopReport()
|
||||||
|
@ -700,26 +774,16 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaPlayerTimeChanged(_ aNotification: Notification!) {
|
func mediaPlayerTimeChanged(_ aNotification: Notification!) {
|
||||||
let time = mediaPlayer.position
|
let time = mediaPlayer.position
|
||||||
if time != lastTime {
|
if abs(time-lastTime) > 0.00005 {
|
||||||
paused = false
|
paused = false
|
||||||
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||||
seekSlider.setValue(mediaPlayer.position, animated: true)
|
seekSlider.setValue(mediaPlayer.position, animated: true)
|
||||||
delegate?.hideLoadingView(self)
|
delegate?.hideLoadingView(self)
|
||||||
|
|
||||||
let remainingTime = abs(mediaPlayer.remainingTime.intValue)/1000
|
timeText.text = String(mediaPlayer.remainingTime.stringValue.dropFirst())
|
||||||
let hours = remainingTime / 3600
|
|
||||||
let minutes = (remainingTime % 3600) / 60
|
|
||||||
let seconds = (remainingTime % 3600) % 60
|
|
||||||
var timeTextStr = ""
|
|
||||||
if hours != 0 {
|
|
||||||
timeTextStr = "\(Int(hours)):\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"
|
|
||||||
} else {
|
|
||||||
timeTextStr = "\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"
|
|
||||||
}
|
|
||||||
timeText.text = timeTextStr
|
|
||||||
|
|
||||||
if CACurrentMediaTime() - controlsAppearTime > 5 {
|
if CACurrentMediaTime() - controlsAppearTime > 5 {
|
||||||
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
|
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
|
||||||
|
@ -730,61 +794,28 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
})
|
})
|
||||||
controlsAppearTime = 999_999_999_999_999
|
controlsAppearTime = 999_999_999_999_999
|
||||||
}
|
}
|
||||||
} else {
|
lastTime = time
|
||||||
paused = true
|
|
||||||
}
|
}
|
||||||
lastTime = time
|
|
||||||
|
|
||||||
if CACurrentMediaTime() - lastProgressReportTime > 5 {
|
if CACurrentMediaTime() - lastProgressReportTime > 5 {
|
||||||
sendProgressReport(eventName: "timeupdate")
|
sendProgressReport(eventName: "timeupdate")
|
||||||
lastProgressReportTime = CACurrentMediaTime()
|
lastProgressReportTime = CACurrentMediaTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Jellyfin Playstate updates
|
|
||||||
func sendProgressReport(eventName: String) {
|
|
||||||
if (eventName == "timeupdate" && mediaPlayer.state == .playing) || eventName != "timeupdate" {
|
|
||||||
let progressInfo = PlaybackProgressInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: (mediaPlayer.state == .paused), isMuted: false, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
|
||||||
|
|
||||||
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: progressInfo)
|
|
||||||
.sink(receiveCompletion: { result in
|
|
||||||
print(result)
|
|
||||||
}, receiveValue: { _ in
|
|
||||||
print("Playback progress report sent!")
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendStopReport() {
|
|
||||||
let stopInfo = PlaybackStopInfo(item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), liveStreamId: nil, playSessionId: playSessionId, failed: nil, nextMediaType: nil, playlistItemId: "playlistItem0", nowPlayingQueue: [])
|
|
||||||
|
|
||||||
PlaystateAPI.reportPlaybackStopped(playbackStopInfo: stopInfo)
|
|
||||||
.sink(receiveCompletion: { result in
|
|
||||||
print(result)
|
|
||||||
}, receiveValue: { _ in
|
|
||||||
print("Playback stop report sent!")
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendPlayReport() {
|
|
||||||
startTime = Int(Date().timeIntervalSince1970) * 10000000
|
|
||||||
|
|
||||||
print("sending play report!")
|
|
||||||
|
|
||||||
let startInfo = PlaybackStartInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: false, isMuted: false, positionTicks: manifest.userData?.playbackPositionTicks, playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
|
||||||
|
|
||||||
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
|
||||||
.sink(receiveCompletion: { result in
|
|
||||||
print(result)
|
|
||||||
}, receiveValue: { _ in
|
|
||||||
print("Playback start report sent!")
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: End VideoPlayerVC
|
||||||
struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
||||||
var item: BaseItemDto
|
var item: BaseItemDto
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
@ -830,3 +861,48 @@ struct VLCPlayerWithControls: UIViewControllerRepresentable {
|
||||||
func updateUIViewController(_ uiViewController: VLCPlayerWithControls.UIViewControllerType, context: UIViewControllerRepresentableContext<VLCPlayerWithControls>) {
|
func updateUIViewController(_ uiViewController: VLCPlayerWithControls.UIViewControllerType, context: UIViewControllerRepresentableContext<VLCPlayerWithControls>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - Play State Update Methods
|
||||||
|
extension PlayerViewController {
|
||||||
|
func sendProgressReport(eventName: String) {
|
||||||
|
if (eventName == "timeupdate" && mediaPlayer.state == .playing) || eventName != "timeupdate" {
|
||||||
|
let progressInfo = PlaybackProgressInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: (mediaPlayer.state == .paused), isMuted: false, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
||||||
|
|
||||||
|
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: progressInfo)
|
||||||
|
.sink(receiveCompletion: { result in
|
||||||
|
print(result)
|
||||||
|
}, receiveValue: { _ in
|
||||||
|
print("Playback progress report sent!")
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendStopReport() {
|
||||||
|
let stopInfo = PlaybackStopInfo(item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, positionTicks: Int64(mediaPlayer.position * Float(manifest.runTimeTicks!)), liveStreamId: nil, playSessionId: playSessionId, failed: nil, nextMediaType: nil, playlistItemId: "playlistItem0", nowPlayingQueue: [])
|
||||||
|
|
||||||
|
PlaystateAPI.reportPlaybackStopped(playbackStopInfo: stopInfo)
|
||||||
|
.sink(receiveCompletion: { result in
|
||||||
|
print(result)
|
||||||
|
}, receiveValue: { _ in
|
||||||
|
print("Playback stop report sent!")
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPlayReport() {
|
||||||
|
startTime = Int(Date().timeIntervalSince1970) * 10000000
|
||||||
|
|
||||||
|
print("sending play report!")
|
||||||
|
|
||||||
|
let startInfo = PlaybackStartInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: false, isMuted: false, positionTicks: manifest.userData?.playbackPositionTicks, playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
||||||
|
|
||||||
|
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
||||||
|
.sink(receiveCompletion: { result in
|
||||||
|
print(result)
|
||||||
|
}, receiveValue: { _ in
|
||||||
|
print("Playback start report sent!")
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,25 +42,34 @@ struct VideoPlayerCastDeviceSelector: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(delegate.discoveredCastDevices, id: \.id) { device in
|
Group {
|
||||||
HStack() {
|
if(!delegate.discoveredCastDevices.isEmpty) {
|
||||||
Text("\(device.name)")
|
List(delegate.discoveredCastDevices, id: \.deviceID) { device in
|
||||||
.font(.subheadline)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
Spacer()
|
|
||||||
Button {
|
|
||||||
delegate.selectedCastDevice = device
|
|
||||||
self.delegate?.castDeviceChanged()
|
|
||||||
self.delegate?.castPopoverDismissed()
|
|
||||||
} label: {
|
|
||||||
HStack() {
|
HStack() {
|
||||||
Text("Connect")
|
Text(device.friendlyName!)
|
||||||
.font(.caption)
|
.font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
Image(systemName: "bonjour")
|
Spacer()
|
||||||
.font(.caption)
|
Button {
|
||||||
|
delegate.selectedCastDevice = device
|
||||||
|
delegate?.castDeviceChanged()
|
||||||
|
delegate?.castPopoverDismissed()
|
||||||
|
} label: {
|
||||||
|
HStack() {
|
||||||
|
Text("Connect")
|
||||||
|
.font(.caption)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
Image(systemName: "bonjour")
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Text("No Cast devices found..")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.font(.subheadline)
|
||||||
|
.fontWeight(.medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
@ -69,7 +78,7 @@ struct VideoPlayerCastDeviceSelector: View {
|
||||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
ToolbarItemGroup(placement: .navigationBarLeading) {
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
Button {
|
Button {
|
||||||
self.delegate?.castPopoverDismissed()
|
delegate?.castPopoverDismissed()
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "chevron.left")
|
Image(systemName: "chevron.left")
|
||||||
|
|
Loading…
Reference in New Issue