Allow frontend to open native camera player through external bus (#4415)

<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Bruno Pantaleão Gonçalves
2026-03-05 15:35:12 +01:00
committed by GitHub
parent 3f310d3968
commit 78b47849eb
4 changed files with 52 additions and 1 deletions

View File

@@ -27,6 +27,7 @@ enum WebViewExternalBusMessage: String, CaseIterable {
case toastHide = "toast/hide"
case entityAddToGetActions = "entity/add_to/get_actions"
case entityAddTo = "entity/add_to"
case cameraPlayerShow = "camera/show"
@MainActor static var configResult: [String: Any] {
[
@@ -38,6 +39,13 @@ enum WebViewExternalBusMessage: String, CaseIterable {
"canTransferThreadCredentialsToKeychain": Current.matter
.threadCredentialsStoreInKeychainEnabled,
"hasAssist": true,
"hasCameraPlayer": {
if #available(iOS 16.0, *), !Current.isCatalyst {
return true
} else {
return false
}
}(),
"canSetupImprov": true,
"downloadFileSupported": true,
"hasEntityAddTo": true,

View File

@@ -178,6 +178,13 @@ final class WebViewExternalMessageHandler: @preconcurrency WebViewExternalMessag
return
}
handleEntityAddTo(entityId: entityId, appPayload: appPayload)
case .cameraPlayerShow:
guard #available(iOS 16.0, *) else { return }
guard let entityId = incomingMessage.Payload?["entity_id"] as? String else {
Current.Log.error("Received camera/show but entity_id was not string! \(incomingMessage)")
return
}
showCameraPlayer(entityId: entityId, cameraName: incomingMessage.Payload?["camera_name"] as? String)
}
} else {
Current.Log.error("unknown: \(incomingMessage.MessageType)")
@@ -604,6 +611,22 @@ final class WebViewExternalMessageHandler: @preconcurrency WebViewExternalMessag
Current.Log.error("Failed to decode entity add to action: \(error)")
}
}
@available(iOS 16.0, *)
private func showCameraPlayer(entityId: String, cameraName: String?) {
guard let webViewController else {
Current.Log.error("WebViewController not available while opening camera player")
return
}
let view = CameraPlayerView(
server: webViewController.server,
cameraEntityId: entityId,
cameraName: cameraName
).embeddedInHostingController()
view.modalPresentationStyle = .overFullScreen
webViewController.presentOverlayController(controller: view, animated: true)
}
}
extension WebViewExternalMessageHandler: @preconcurrency ImprovManagerDelegate {

View File

@@ -30,8 +30,9 @@ final class WebViewExternalBusMessageTests: XCTestCase {
XCTAssertEqual(WebViewExternalBusMessage.toastHide.rawValue, "toast/hide")
XCTAssertEqual(WebViewExternalBusMessage.entityAddToGetActions.rawValue, "entity/add_to/get_actions")
XCTAssertEqual(WebViewExternalBusMessage.entityAddTo.rawValue, "entity/add_to")
XCTAssertEqual(WebViewExternalBusMessage.cameraPlayerShow.rawValue, "camera/show")
XCTAssertEqual(WebViewExternalBusMessage.allCases.count, 21)
XCTAssertEqual(WebViewExternalBusMessage.allCases.count, 22)
}
func testExternalBusOutgoingMessageKeys() {
@@ -64,6 +65,7 @@ final class WebViewExternalBusMessageTests: XCTestCase {
"hasBarCodeScanner",
"canTransferThreadCredentialsToKeychain",
"hasAssist",
"hasCameraPlayer",
"canSetupImprov",
"downloadFileSupported",
"hasEntityAddTo",

View File

@@ -170,4 +170,22 @@ final class WebViewExternalMessageHandlerTests: XCTestCase {
XCTAssertTrue(mockWebViewController.overlayedController is UIHostingController<AssistView>)
}
@MainActor func testHandleExternalMessageCameraPlayerShowPresentsCameraPlayer() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "camera/show",
"payload": [
"entity_id": "camera.front_door",
"camera_name": "Front Door",
],
]
sut.handleExternalMessage(dictionary)
XCTAssertNotNil(mockWebViewController.overlayedController)
XCTAssertEqual(mockWebViewController.overlayedController?.modalPresentationStyle, .overFullScreen)
}
}