Skip to content

Commit

Permalink
iOS TvCasting app: Send Content Launch By URL request and Open commis…
Browse files Browse the repository at this point in the history
…sioning window (#19779)

* iOS TvCasting app: Open commissioning window and send Content Launch ByURL request

* Added docs, fixed typos
  • Loading branch information
sharadb-amazon authored and pull[bot] committed Aug 3, 2022
1 parent f986b80 commit 2210324
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,86 @@

@interface CastingServerBridge : NSObject

@property void (^_Nullable commissioningCompleteCallback)(bool);

@property void (^_Nullable launchUrlResponseCallback)(bool);

+ (CastingServerBridge * _Nullable)getSharedInstance;

/*!
@brief Browse for on-network commissioner TVs
@param clientQueue Queue to dispatch the call to the discoveryRequestSentHandler on
@param discoveryRequestSentHandler Handler to call after the Commissioner discovery request has been sent
*/
- (void)discoverCommissioners:(dispatch_queue_t _Nonnull)clientQueue
discoveryRequestSentHandler:(nullable void (^)(bool))discoveryRequestSentHandler;

/*!
@brief Retrieve a discovered commissioner TV
@param index Index in the list of discovered commissioners
@param clientQueue Queue to dispatch the call to the discoveredCommissionerHandler on
@param discoveredCommissionerHandler Handler to call after a discovered commissioner has been retrieved
*/
- (void)getDiscoveredCommissioner:(int)index
clientQueue:(dispatch_queue_t _Nonnull)clientQueue
discoveredCommissionerHandler:(nullable void (^)(DiscoveredNodeData * _Nullable))discoveredCommissionerHandler;

/*!
@brief Send a User Directed Commissioning request to a commissioner TV
@param commissionerIpAddress IP address of the commissioner
@param commissionerPort Port number at which the commissioner is listening for User Directed Commissioning requests
@param platformInterface Platform representation of the commissioner's IP address's interface
@param clientQueue Queue to dispatch the call to the udcRequestSentHandler on
@param udcRequestSentHandler Handler to call on sending the User Directed Commissioning request
*/
- (void)sendUserDirectedCommissioningRequest:(NSString * _Nonnull)commissionerIpAddress
commissionerPort:(uint16_t)commissionerPort
platformInterface:(unsigned int)platformInterface
clientQueue:(dispatch_queue_t _Nonnull)clientQueue
udcRequestSentHandler:(nullable void (^)(bool))udcRequestSentHandler;

/*!
@brief Request opening of a basic commissioning window
@param commissioningCompleteCallback Callback for when commissioning of this app has been completed via a call to the general
commissioning cluster (by usually an on-network TV/Media device acting as a Matter commissioner)
@param clientQueue Queue to dispatch the call to the commissioningWindowRequestedHandler on
@param commissioningWindowRequestedHandler Handler to call on requesting the opening of a commissioning window
*/
- (void)openBasicCommissioningWindow:(nullable void (^)(bool))commissioningCompleteCallback
clientQueue:(dispatch_queue_t _Nonnull)clientQueue
commissioningWindowRequestedHandler:(nullable void (^)(bool))commissioningWindowRequestedHandler;

/*!
@brief Send a Content Launcher:LaunchURL request to a TV
@param contentUrl URL of the content to launch on the TV
@param contentDisplayStr Display string value corresponding to the content
@param launchUrlResponseCallback Callback for when the Launch URL response has been received
@param clientQueue Queue to dispatch the call to the launchUrlRequestSentHandler on
@param launchUrlRequestSentHandler Handler to call on sending the Launch URL request
*/
- (void)contentLauncherLaunchUrl:(NSString * _Nonnull)contentUrl
contentDisplayStr:(NSString * _Nonnull)contentDisplayStr
launchUrlResponseCallback:(nullable void (^)(bool))launchUrlResponseCallback
clientQueue:(dispatch_queue_t _Nonnull)clientQueue
launchUrlRequestSentHandler:(nullable void (^)(bool))launchUrlRequestSentHandler;
@end

#endif /* CastingServerBridge_h */
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ - (void)sendUserDirectedCommissioningRequest:(NSString * _Nonnull)commissionerIp
clientQueue:(dispatch_queue_t _Nonnull)clientQueue
udcRequestSentHandler:(nullable void (^)(bool))udcRequestSentHandler
{
ChipLogProgress(
AppServer, "CastingServerBridge().sendUserDirectedCommissioningRequest() called with port %d", commissionerPort);
ChipLogProgress(AppServer,
"CastingServerBridge().sendUserDirectedCommissioningRequest() called with IP %s port %d platformInterface %d",
[commissionerIpAddress UTF8String], commissionerPort, platformInterface);

dispatch_async(chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{
dispatch_async(_chipWorkQueue, ^{
bool udcRequestStatus;
chip::Inet::IPAddress commissionerAddrInet;
if (chip::Inet::IPAddress::FromString([commissionerIpAddress UTF8String], commissionerAddrInet) == false) {
Expand All @@ -153,4 +154,39 @@ - (void)sendUserDirectedCommissioningRequest:(NSString * _Nonnull)commissionerIp
});
});
}

- (void)openBasicCommissioningWindow:(nullable void (^)(bool))commissioningCompleteCallback
clientQueue:(dispatch_queue_t _Nonnull)clientQueue
commissioningWindowRequestedHandler:(nullable void (^)(bool))commissioningWindowRequestedHandler
{
ChipLogProgress(AppServer, "CastingServerBridge().openBasicCommissioningWindow() called");

dispatch_async(_chipWorkQueue, ^{
CHIP_ERROR err = CastingServer::GetInstance()->OpenBasicCommissioningWindow(
[&commissioningCompleteCallback](CHIP_ERROR err) { commissioningCompleteCallback(CHIP_NO_ERROR == err); });

dispatch_async(clientQueue, ^{
commissioningWindowRequestedHandler(CHIP_NO_ERROR == err);
});
});
}

- (void)contentLauncherLaunchUrl:(NSString * _Nonnull)contentUrl
contentDisplayStr:(NSString * _Nonnull)contentDisplayStr
launchUrlResponseCallback:(nullable void (^)(bool))launchUrlResponseCallback
clientQueue:(dispatch_queue_t _Nonnull)clientQueue
launchUrlRequestSentHandler:(nullable void (^)(bool))launchUrlRequestSentHandler
{
ChipLogProgress(AppServer, "CastingServerBridge().contentLauncherLaunchUrl() called");

dispatch_async(_chipWorkQueue, ^{
CHIP_ERROR err
= CastingServer::GetInstance()->ContentLauncherLaunchURL([contentUrl UTF8String], [contentDisplayStr UTF8String],
[&launchUrlResponseCallback](CHIP_ERROR err) { launchUrlResponseCallback(CHIP_NO_ERROR == err); });
dispatch_async(clientQueue, ^{
launchUrlRequestSentHandler(CHIP_NO_ERROR == err);
});
});
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
3C7507B92853EFF000D7DB3A /* CommissioningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C7507B82853EFF000D7DB3A /* CommissioningViewModel.swift */; };
3C7507BC2857A6EE00D7DB3A /* DiscoveredNodeDataConverter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C7507BB2857A6EE00D7DB3A /* DiscoveredNodeDataConverter.mm */; };
3C9ACC05284ABF4000718B2D /* libTvCastingCommon.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C9ACC04284ABF2F00718B2D /* libTvCastingCommon.a */; };
3CA19435285BA780004768D5 /* ContentLauncherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA19434285BA780004768D5 /* ContentLauncherView.swift */; };
3CA19437285BA877004768D5 /* ContentLauncherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA19436285BA877004768D5 /* ContentLauncherViewModel.swift */; };
3CC0E8FA2841DD3400EC6A18 /* TvCastingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC0E8F92841DD3400EC6A18 /* TvCastingApp.swift */; };
3CC0E8FC2841DD3400EC6A18 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC0E8FB2841DD3400EC6A18 /* ContentView.swift */; };
3CC0E8FE2841DD3500EC6A18 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3CC0E8FD2841DD3500EC6A18 /* Assets.xcassets */; };
Expand All @@ -34,6 +36,8 @@
3C7507BB2857A6EE00D7DB3A /* DiscoveredNodeDataConverter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DiscoveredNodeDataConverter.mm; sourceTree = "<group>"; };
3C7507BD2857A72A00D7DB3A /* DiscoveredNodeDataConverter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiscoveredNodeDataConverter.hpp; sourceTree = "<group>"; };
3C9ACC04284ABF2F00718B2D /* libTvCastingCommon.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libTvCastingCommon.a; path = lib/libTvCastingCommon.a; sourceTree = BUILT_PRODUCTS_DIR; };
3CA19434285BA780004768D5 /* ContentLauncherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLauncherView.swift; sourceTree = "<group>"; };
3CA19436285BA877004768D5 /* ContentLauncherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLauncherViewModel.swift; sourceTree = "<group>"; };
3CC0E8F62841DD3400EC6A18 /* TvCasting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TvCasting.app; sourceTree = BUILT_PRODUCTS_DIR; };
3CC0E8F92841DD3400EC6A18 /* TvCastingApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TvCastingApp.swift; sourceTree = "<group>"; };
3CC0E8FB2841DD3400EC6A18 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -98,6 +102,8 @@
3C7507B62853A3AD00D7DB3A /* CommissionerDiscoveryViewModel.swift */,
3C7507AE28529A5F00D7DB3A /* CommissioningView.swift */,
3C7507B82853EFF000D7DB3A /* CommissioningViewModel.swift */,
3CA19434285BA780004768D5 /* ContentLauncherView.swift */,
3CA19436285BA877004768D5 /* ContentLauncherViewModel.swift */,
);
path = TvCasting;
sourceTree = "<group>";
Expand Down Expand Up @@ -237,6 +243,8 @@
3C7507B92853EFF000D7DB3A /* CommissioningViewModel.swift in Sources */,
3C7507BC2857A6EE00D7DB3A /* DiscoveredNodeDataConverter.mm in Sources */,
3C7507A72851188500D7DB3A /* DiscoveredNodeData.mm in Sources */,
3CA19437285BA877004768D5 /* ContentLauncherViewModel.swift in Sources */,
3CA19435285BA780004768D5 /* ContentLauncherView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -280,6 +288,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand All @@ -296,7 +305,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -352,7 +361,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ struct CommissioningView: View {
Text("Failed to open Commissioning window!")
.foregroundColor(Color.red)
}

if(viewModel.commisisoningComplete == true)
{
NavigationLink(
destination: ContentLauncherView(),
label: {
Text("Next")
.frame(width: 75, height: 30, alignment: .center)
.border(Color.black, width: 1)
}
).background(Color.blue)
.foregroundColor(Color.white)
.padding()
}
}
.navigationTitle("Commissioning...")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,34 @@


import Foundation
import os.log

class CommissioningViewModel: ObservableObject {
let Log = Logger(subsystem: "com.matter.casting",
category: "CommissioningViewModel")

@Published var udcRequestSent: Bool?;

@Published var commisisoningWindowOpened: Bool?;


@Published var commisisoningComplete: Bool?;

func prepareForCommissioning(selectedCommissioner: DiscoveredNodeData?) {
// TBD: Call openBasicCommissioningWindow() and get Onboarding payload
if let castingServerBridge = CastingServerBridge.getSharedInstance()
{
castingServerBridge.openBasicCommissioningWindow(
{ (result: Bool) -> () in
// commissioning complete handler code
self.Log.info("Commissioning status: \(result)")
self.commisisoningComplete = result
},
clientQueue: DispatchQueue.main,
commissioningWindowRequestedHandler: { (result: Bool) -> () in
self.commisisoningWindowOpened = result
})
}

// TBD: Get Onboarding payload

// Send User directed commissioning request if a commissioner with a known IP addr was selected
if(selectedCommissioner != nil && selectedCommissioner!.numIPs > 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import SwiftUI

struct ContentLauncherView: View {
@StateObject var viewModel = ContentLauncherViewModel()

@State private var contentUrl: String = ""
@State private var contentDisplayStr: String = ""

var body: some View {
VStack(alignment: .leading) {
HStack() {
Text("Content URL")

TextField(
"https://www.test.com/videoid",
text: $contentUrl
)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.border(.secondary)
}

HStack() {
Text("Display string")

TextField(
"Test video",
text: $contentDisplayStr
)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.border(.secondary)
}

Button("Launch URL!") {
viewModel.launchUrl(contentUrl: contentUrl, contentDisplayStr: contentDisplayStr)
}
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(4)
.border(Color.black, width: 1)
.padding()

Text(viewModel.status ?? "")
}
.navigationTitle("Content Launcher")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)
}
}

struct ContentLauncherView_Previews: PreviewProvider {
static var previews: some View {
ContentLauncherView()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


import Foundation
import os.log

class ContentLauncherViewModel: ObservableObject {
let Log = Logger(subsystem: "com.matter.casting",
category: "ContentLauncherViewModel")

@Published var status: String?;

func launchUrl(contentUrl: String?, contentDisplayStr: String?)
{
if ((contentUrl != nil && !contentUrl!.isEmpty) && (contentDisplayStr != nil && !contentDisplayStr!.isEmpty))
{
if let castingServerBridge = CastingServerBridge.getSharedInstance()
{
castingServerBridge
.contentLauncherLaunchUrl(contentUrl!,
contentDisplayStr: contentDisplayStr!,
launchUrlResponseCallback:
{ (result: Bool) -> () in
self.Log.info("ContentLauncherViewModel.launchUrl.launchUrlResponseCallback result \(result)")
self.status = result ? "Launched URL successfully" : "Launch URL failure!"
},
clientQueue: DispatchQueue.main,
launchUrlRequestSentHandler:
{ (result: Bool) -> () in
self.Log.info("ContentLauncherViewModel.launchUrl.launcUrlRequestSentHandler result \(result)")
self.status = result ? "Sent Launch URL request" : "Failed to send Launch URL request!"
})
}
}
else
{
Log.debug("ContentLauncherViewModel.launchUrl input(s) missing!")
self.status = "Missing input parameter(s)!"
}
}
}
Loading

0 comments on commit 2210324

Please sign in to comment.