Files
iOS/Sources/App/Cameras/CameraPlayer/CameraPlayerView.swift
Bruno Pantaleão Gonçalves 3c62221a01 Drop usage of entity_registry in favor of list_for_display + drop friendly_name usage (#4734)
<!-- 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. -->
2026-06-11 17:31:32 +02:00

174 lines
5.6 KiB
Swift

import GRDB
import SFSafeSymbols
import Shared
import SwiftUI
/// A camera player view that automatically falls back from WebRTC to HLS to MJPEG
/// when a streaming method is not supported.
@available(iOS 16.0, *)
struct CameraPlayerView: View {
@Environment(\.dismiss) private var dismiss
private let server: Server
private let cameraEntityId: String
private let cameraName: String?
@State private var playerType: PlayerType = .webRTC
@State private var appEntity: HAAppEntity?
@State private var name: String?
@State private var controlsVisible = true
@State private var showLoader = true
enum PlayerType {
case webRTC
case hls
case mjpeg
}
init(server: Server, cameraEntityId: String, cameraName: String? = nil) {
self.server = server
self.cameraEntityId = cameraEntityId
self.cameraName = cameraName
}
var body: some View {
ZStack {
ZStack(alignment: .topLeading) {
NavigationStack {
content
.toolbar {
ToolbarItem(placement: .primaryAction) {
if controlsVisible {
HStack(spacing: DesignSystem.Spaces.one) {
Button {
openMoreInfo()
} label: {
Image(systemSymbol: .safari)
}
}
}
}
ToolbarItem(placement: .primaryAction) {
CloseButton {
dismiss()
}
}
}
.modify { view in
if #available(iOS 18.0, *) {
view.toolbarVisibility(controlsVisible ? .automatic : .hidden, for: .navigationBar)
} else {
view
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
nameBadge
}
if showLoader {
HAProgressView(style: .extraLarge)
}
}
.onAppear {
appEntity = HAAppEntity.entity(id: cameraEntityId, serverId: server.identifier.rawValue)
name = appEntity?.name ?? cameraName
}
.statusBarHidden(true)
.modify { view in
if #available(iOS 16.0, *) {
view.persistentSystemOverlays(.hidden)
} else {
view
}
}
}
@ViewBuilder
private var nameBadge: some View {
if let name, controlsVisible {
Text(name)
.font(.headline)
.padding(.horizontal, DesignSystem.Spaces.two)
.padding(.vertical, DesignSystem.Spaces.one)
.modify { view in
if #available(iOS 26.0, *) {
view
.glassEffect(.regular.interactive(), in: .capsule)
} else {
view
.background(.regularMaterial)
.clipShape(.capsule)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, DesignSystem.Spaces.two)
.padding(.top, DesignSystem.Spaces.half)
}
}
private var content: some View {
Group {
switch playerType {
case .webRTC:
WebRTCVideoPlayerView(
server: server,
cameraEntityId: cameraEntityId,
cameraName: cameraName,
controlsVisible: $controlsVisible,
showLoader: $showLoader,
onWebRTCUnsupported: {
fallbackToHLS()
}
)
case .hls:
CameraStreamHLSView(
server: server,
cameraEntityId: cameraEntityId,
cameraName: cameraName,
onHLSUnsupported: {
fallbackToMJPEG()
}
)
case .mjpeg:
CameraMJPEGPlayerView(
server: server,
cameraEntityId: cameraEntityId,
cameraName: cameraName
)
}
}
}
private func fallbackToHLS() {
Current.Log.info("Camera \(cameraEntityId) does not support WebRTC, falling back to HLS")
withAnimation {
playerType = .hls
}
}
private func fallbackToMJPEG() {
Current.Log.info("Camera \(cameraEntityId) does not support HLS, falling back to MJPEG")
withAnimation {
playerType = .mjpeg
}
}
private func openMoreInfo() {
if let url = AppConstants
.openEntityDeeplinkURL(entityId: cameraEntityId, serverId: server.identifier.rawValue) {
URLOpener.shared.open(url, options: [:], completionHandler: nil)
}
}
}
#if DEBUG
@available(iOS 16.0, *)
#Preview {
CameraPlayerView(
server: ServerFixture.standard,
cameraEntityId: "camera.front_door",
cameraName: "Front Door"
)
}
#endif