Guide de l'appareil photo pour iOS

Le type d'appareil "Caméra" est implémenté à l'aide de deux traits : PushAvStreamTransportTrait, qui gère le transport des flux audio et vidéo à l'aide de protocoles basés sur le push, et WebRtcLiveViewTrait, qui permet de contrôler les diffusions en direct et la fonctionnalité TalkBack.

Vérifiez toujours si un appareil est compatible avec les attributs et les commandes avant d'utiliser des fonctionnalités ou de tenter de mettre à jour des attributs. Pour en savoir plus, consultez Contrôler les appareils suriOS.

Type d'appareil des API Home Traits Application exemple Swift Cas d'utilisation

Appareil photo

GoogleCameraDeviceType

home.matter.6006.types.0158

Appareil qui prend des photos ou enregistre des vidéos. Les caméras peuvent proposer des flux vidéo en direct accessibles, une fonctionnalité d'interphone ou des événements de détection.

Traits requis
     google PushAvStreamTransportTrait
     google WebRtcLiveViewTrait

Appareil photo

Lancer une diffusion en direct

Pour lancer une diffusion en direct, envoyez la chaîne SDP (Session Description Protocol) à la méthode startLiveView(offerSdp:) du trait WebRtcLiveViewTrait, qui renvoie trois valeurs :

  • SDP de la session.
  • Durée de la session en secondes.
  • ID de la session, qui peut être utilisé pour la prolonger ou la terminer.
public func sendOffer(offerSdp: String) async throws
-> (answerSdp: String, mediaSessionId: String, liveViewDuration: TimeInterval)
{
  do {
    // Sending StartLiveView command
    let response = try await liveViewTrait.startLiveView(
      offerSdp: offerSdp
    )
    // Received StartLiveView response
    return (
      answerSdp: response.answerSdp,
      mediaSessionId: response.mediaSessionId,
      liveViewDuration: TimeInterval(response.liveSessionDurationSeconds)
    )
  } catch {
    // Failed to send StartLiveView command
    throw error
  }
}

Prolonger une diffusion en direct

Les diffusions en direct ont une durée prédéfinie au terme de laquelle elles expirent. Pour prolonger la durée d'un flux actif, envoyez une demande de prolongation à l'aide de la méthode extendLiveView(mediaSessionId:optionalArgsProvider:) :

public func extendLiveView(mediaSessionId: String) async throws {
  do {
    // Extending live view
    let extendedDuration = try await liveViewTrait.extendLiveView(mediaSessionId: mediaSessionId)
  } catch {
    // Failed to extend live view
    throw error
  }
}

Démarrer et arrêter TalkBack

Pour démarrer TalkBack, appelez la méthode startTalkback(mediaSessionId:optionalArgsProvider:) du trait WebRtcLiveViewTrait. Pour arrêter, utilisez stopTalkback(mediaSessionId:).

public func toggleTwoWayTalk(isOn: Bool, mediaSessionId: String) async throws {
  do {
    if isOn {
      try await liveViewTrait.startTalkback(mediaSessionId: mediaSessionId)
    } else {
      try await liveViewTrait.stopTalkback(mediaSessionId: mediaSessionId)
    }
  } catch {
    throw HomeError.commandFailed("Failed to toggle twoWayTalk: \(error)")
  }
}

Activer et désactiver la fonctionnalité d'enregistrement

Pour activer la fonctionnalité d'enregistrement de la caméra, transmettez TransportStatusEnum.Active à la méthode setTransportStatus(transportStatus:optionalArgsProvider:) du trait PushAvStreamTransportTrait. Pour désactiver la fonctionnalité d'enregistrement, transmettez-la TransportStatusEnum.Inactive. Dans l'exemple suivant, nous encapsulons ces appels dans un seul appel qui utilise un Boolean pour activer ou désactiver la fonctionnalité d'enregistrement :

public func toggleIsRecording(isOn: Bool) {
  self.uiState = .loading

  guard let pushAvStreamTransportTrait else {
    // PushAvStreamTransportTrait not found.
    return
  }
  Task {
    do {
      try await pushAvStreamTransportTrait.setTransportStatus(
        transportStatus: isOn ? .active : .inactive)
      if isOn {
        do {
          self.player = try self.createWebRtcPlayer()
        } catch {
          // Failed to initialize WebRtcPlayer
          self.uiState = .disconnected
          return
        }
        await self.player?.initialize()
        self.uiState = .live
      } else {
        self.player = nil
        self.uiState = .off
      }
    } catch {
      // Failed to toggle onOff
    }
  }
}

Activer ou désactiver la capacité d'enregistrement de la caméra revient à activer ou désactiver la vidéo de la caméra. Lorsque la vidéo d'une caméra est activée, elle enregistre (pour les événements et les extraits associés).

Lorsque la fonctionnalité d'enregistrement est désactivée (la vidéo de la caméra est désactivée) :

  • La caméra peut toujours s'afficher comme étant en ligne selon le connectivityState du type d'appareil.
  • Le flux vidéo en direct n'est pas accessible et la caméra ne détecte aucun événement cloud.

Vérifier si la fonctionnalité d'enregistrement est activée

Pour savoir si la fonctionnalité d'enregistrement d'une caméra est activée, vérifiez si des connexions sont actives. L'exemple suivant définit deux fonctions pour ce faire :

public func isDeviceRecording() -> Bool {
  guard let pushAvStreamTransportTrait else {
    // PushAvStreamTransportTrait not found.
    return false
  }
  guard
    let hasActiveConnection =
      pushAvStreamTransportTrait
      .attributes
      .currentConnections?
      .contains(where: { $0.transportStatus == .active })
  else {
    return false
  }
  return hasActiveConnection
}

Paramètres audio

Différents paramètres audio de la caméra peuvent être contrôlés via les API Home.

Activer ou désactiver le micro

Pour activer ou désactiver le micro de l'appareil, mettez à jour l'attribut microphoneMuted du trait CameraAvStreamManagementTrait à l'aide de la fonction setMicrophoneMuted intégrée :

// Turn the device's microphone on or off
func setMicrophone(on: Bool) async {
  do {
    _ = try await self.cameraAvStreamManagementTrait?.update {
      $0.setMicrophoneMuted(!on)
    }
  } catch {
    // Error
  }
}

Activer ou désactiver l'enregistrement audio

Pour activer ou désactiver l'enregistrement audio sur l'appareil, mettez à jour l'attribut recordingMicrophoneMuted du trait CameraAvStreamManagementTrait à l'aide de la fonction setRecordingMicrophoneMuted intégrée :

// Turn audio recording on or off for the device
func setAudioRecording(on: Bool) async {
  do {
    _ = try await self.cameraAvStreamManagementTrait?.update {
      $0.setRecordingMicrophoneMuted(!on)
    }
  } catch {
    // Error
  }
}

Régler le volume du haut-parleur

Pour ajuster le volume du haut-parleur de l'appareil, mettez à jour l'attribut speakerVolumeLevel du trait CameraAvStreamManagementTrait à l'aide de la fonction setSpeakerVolumeLevel intégrée :

// Adjust the camera speaker volume
func setSpeakerVolume(to value: UInt8) async {
  do {
    _ = try await cameraAvStreamManagementTrait.update {
      $0.setSpeakerVolumeLevel(value)
    }
  } catch {
    // Error
  }
}

Autres paramètres

Vous pouvez contrôler d'autres paramètres de la caméra à l'aide des API Home.

Modifier l'orientation de l'image

Vous pouvez faire pivoter l'orientation de l'image (vidéo) de la caméra. La vidéo ne peut être pivotée que de 180 degrés.

Pour modifier l'orientation de l'image de la caméra, mettez à jour l'attribut imageRotation du trait CameraAvStreamManagementTrait à l'aide de la fonction setImageRotation intégrée :

// Change the camera's image orientation
// Value must be 0 or 180
func setImageRotation(to value: UInt16) async {
  do {
    _ = try await cameraAvStreamManagementTrait.update {
      $0.setImageRotation(value)
    }
  } catch {
    // Error
  }
}

Activer ou désactiver la vision nocturne

Pour activer ou désactiver la vision nocturne de la caméra, utilisez TriStateAutoEnum pour mettre à jour l'attribut nightVision du trait CameraAvStreamManagementTrait à l'aide de la fonction setNightVision intégrée :

// Turn night vision on or off
func setNightVision(
  to value: Google.CameraAvStreamManagementTrait.TriStateAutoEnum
) async {
  do {
    _ = try await cameraAvStreamManagementTrait.update {
      $0.setNightVision(value)
    }
  } catch {
    // Error
  }
}

Modifier la luminosité du voyant d'état

Pour modifier la luminosité du voyant d'état, utilisez ThreeLevelAutoEnum pour mettre à jour l'attribut statusLightBrightness du trait CameraAvStreamManagementTrait à l'aide de la fonction setStatusLightBrightness intégrée :

// Set the LED brightness
func setStatusLightBrightness(
  to value: Google.CameraAvStreamManagementTrait.ThreeLevelAutoEnum
) async {
  do {
    _ = try await cameraAvStreamManagementTrait.update {
      $0.setStatusLightBrightness(value)
    }
  } catch {
    // Error
  }
}

Modifier la fenêtre d'affichage de la caméra

Le champ de vision de la caméra est identique à la fonctionnalité Zoomer et recadrer décrite dans l'article d'assistance Zoomer et améliorer l'affichage vidéo de la caméra Nest.

La fenêtre d'affichage est définie dans un ViewportStruct qui contient quatre valeurs utilisées comme coordonnées de la fenêtre d'affichage. Les coordonnées sont définies comme suit :

(x1,y1) -- (x2,y1)
   |          |
(x1,y2) -- (x2,y2)

La détermination des valeurs pour ViewportStruct dépend de l'UI et de l'implémentation de l'appareil photo d'une application. Pour définir le viewport de la vidéo de la caméra, mettez à jour l'attribut viewport du trait CameraAvStreamManagementTrait avec un ViewportStruct à l'aide de la fonction setViewport intégrée.

func setCrop(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16) {

  let viewport = Google.CameraAvStreamManagementTrait.ViewportStruct(
    x1: x1,
    y1: y1,
    x2: x2,
    y2: y2
  )

  Task {
    do {
      try await cameraAvStreamManagementTrait.update {
        $0.setViewport(viewport)
      }
    } catch {
      // Error
    }
  }

}

Générer un TransportOptionsStruct

Certains paramètres nécessitent de modifier les propriétés d'un TransportOptionsStruct, qui est ensuite transmis aux options de transport d'une connexion de streaming. Pour Swift, cette structure doit être générée avant la mise à jour des propriétés.

Utilisez cette fonction d'assistance pour générer la structure à utiliser avec les modifications de paramètres suivantes :

func getTransportOptions(
  transportOptions: Google.PushAvStreamTransportTrait.TransportOptionsStruct,
  wakeUpSensitivity: UInt8?,
  maxEventLength: UInt32?
) async throws
  -> Google.PushAvStreamTransportTrait.TransportOptionsStruct
{

  var newMotionTimeControl:
    Google.PushAvStreamTransportTrait.TransportMotionTriggerTimeControlStruct? = nil
  if let maxEventLength {
    guard let motionTimeControl = transportOptions.triggerOptions.motionTimeControl else {
      throw HomeError.failedPrecondition(
        // Error - cannot update max event length without motion time control
    }
    newMotionTimeControl =
      Google.PushAvStreamTransportTrait.TransportMotionTriggerTimeControlStruct(
        initialDuration: motionTimeControl.initialDuration,
        augmentationDuration: motionTimeControl.augmentationDuration,
        maxDuration: maxEventLength,
        blindDuration: motionTimeControl.blindDuration
      )
  }

  return Google.PushAvStreamTransportTrait.TransportOptionsStruct(
    streamUsage: .recording,
    videoStreamID: nil,
    audioStreamID: nil,
    tlsEndpointID: transportOptions.tlsEndpointID,
    url: transportOptions.url,
    triggerOptions: Google.PushAvStreamTransportTrait.TransportTriggerOptionsStruct(
      triggerType: .motion,
      motionZones: nil,
      motionSensitivity: wakeUpSensitivity,
      motionTimeControl: newMotionTimeControl,
      maxPreRollLen: nil
    ),
    ingestMethod: .cmafIngest,
    containerOptions: Google.PushAvStreamTransportTrait.ContainerOptionsStruct(
      containerType: .cmaf,
      cmafContainerOptions: nil
    ),
    expiryTime: nil
  )
}

private func getRecordingConnection() async throws
  -> Google.PushAvStreamTransportTrait.TransportConfigurationStruct?
{
  guard let pushAvStreamTransportTrait else {
    // Error - PushAvStreamTransport trait not available
    return nil
  }

  let connections = try await pushAvStreamTransportTrait.findTransport().transportConfigurations

  for connection in connections {
    guard let transportOptions = connection.transportOptions,
      transportOptions.streamUsage == .recording
    else {
      continue
    }

    return connection
  }

  return nil
}

Ajuster la sensibilité d'activation de l'appareil

La sensibilité au réveil de l'appareil permet d'économiser la batterie en réduisant la plage à laquelle l'appareil peut détecter une activité et en augmentant le temps de réveil après la détection de cette activité.

Dans les API Home, cette propriété peut être définie à l'aide de la propriété motionSensitivity de triggerOptions dans transportOptions de l'appareil. Ces options sont définies dans le trait PushAvStreamTransportTrait pour chaque appareil.

La sensibilité à la sortie de veille ne peut être définie que sur les valeurs suivantes :

  • 1 = Faible
  • 5 = Moyen
  • 10 = Élevé

Pour mettre à jour la configuration, vous devez trouver la configuration de transport des flux d'enregistrement actifs à l'aide de la commande findTransport, puis modifier la configuration avec la nouvelle valeur de sensibilité à l'aide de la commande modifyPushTransport.

La commande modifyPushTransport nécessite de transmettre l'intégralité de TransportOptionsStruct. Vous devez donc d'abord copier les valeurs existantes de la configuration actuelle. Consultez Générer un TransportOptionsStruct pour obtenir une fonction d'assistance permettant d'effectuer cette opération.

func setWakeUpSensitivity(to value: UInt8) async {
  do {
    let connection = try await getRecordingConnection()
    guard let connection,
      let transportOptions = connection.transportOptions
    else {
      // Error - Transport options not available
      return
    }

    guard transportOptions.triggerOptions.motionSensitivity != nil else {
      // Error - Motion sensitivity not available to be updated for this device
      return
    }

    try await pushAvStreamTransportTrait.modifyPushTransport(
      connectionID: connection.connectionID,
      transportOptions: self.getTransportOptions(
        transportOptions: transportOptions,
        wakeUpSensitivity: value,
        maxEventLength: nil
      )
    )

  } catch {
    // Error
  }
}

Ajuster la durée maximale des événements

La durée maximale des événements correspond à la durée d'enregistrement vidéo d'un événement par la caméra. Grâce aux API Home, vous pouvez configurer cette durée pour chaque appareil, avec les mêmes valeurs que dans Google Home app (GHA), par intervalles de secondes :

  • 10 secondes
  • 15 secondes
  • 30 secondes
  • 60 secondes (1 minute)
  • 120 secondes (2 minutes)
  • 180 secondes (3 minutes)

Dans les API Home, cette propriété peut être définie à l'aide de la propriété motionTimeControl de triggerOptions dans transportOptions de l'appareil. Ces options sont définies dans le trait PushAvStreamTransportTrait pour chaque appareil.

Pour mettre à jour la configuration, vous devez trouver la configuration de transport des flux d'enregistrement actifs à l'aide de la commande findTransport, puis modifier la configuration avec la nouvelle valeur de durée de l'événement à l'aide de la commande modifyPushTransport.

La commande modifyPushTransport nécessite de transmettre l'intégralité de TransportOptionsStruct. Vous devez donc d'abord copier les valeurs existantes de la configuration actuelle. Consultez Générer un TransportOptionsStruct pour obtenir une fonction d'assistance permettant d'effectuer cette opération.

func setMaxEventLength(to value: UInt32) async {
  do {
    let connection = try await getRecordingConnection()
    guard let connection,
      let transportOptions = connection.transportOptions
    else {
      // Error - Transport options not available
      return
    }

    guard transportOptions.triggerOptions.motionTimeControl != nil else {
      // Error - Motion time control not available to be updated for this device
      return
    }

    try await pushAvStreamTransportTrait.modifyPushTransport(
      connectionID: connection.connectionID,
      transportOptions: self.getTransportOptions(
        transportOptions: transportOptions,
        wakeUpSensitivity: nil,
        maxEventLength: value
      )
    )

  } catch {
    // Error
  }
}