@mentra/acs-meeting 3.2.0-dev.136 → 3.2.0-dev.153

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,14 @@ Phone-native Azure Communication Services client that puts a MentraOS wearer int
4
4
  Microsoft Teams meeting as a guest. The glasses provide the camera and the microphone;
5
5
  the phone does all the WebRTC and ACS work.
6
6
 
7
+ ## Host setup
8
+
9
+ Add `"@mentra/acs-meeting"` to the host's Expo `plugins` list, including when
10
+ this module is installed through `@mentra/engine`, then run Expo prebuild for
11
+ the target platform. The plugin enables Android core library desugaring and
12
+ builds `AzureCommunicationCommon` as the framework required by ACS on iOS.
13
+ These requirements apply even when the host does not use Crust or Mapbox.
14
+
7
15
  The phone is a **relay, not a capture device**. It subscribes to whatever the glasses
8
16
  already published to Cloudflare, decodes it, and re-publishes it into ACS. No frame
9
17
  crosses the JavaScript bridge. Production never generates pixels on the phone.
package/app.plugin.js CHANGED
@@ -1,5 +1,9 @@
1
- const {withPodfile} = require("expo/config-plugins")
2
- const {mergeContents} = require("@expo/config-plugins/build/utils/generateCode")
1
+ const {withAppBuildGradle, withPodfile} = require("expo/config-plugins")
2
+ const {
3
+ createGeneratedHeaderComment,
4
+ mergeContents,
5
+ removeGeneratedContents,
6
+ } = require("@expo/config-plugins/build/utils/generateCode")
3
7
 
4
8
  // Calling is a vendored dynamic framework: its umbrella header and LC_LOAD_DYLIB
5
9
  // both require Common.framework. Expo otherwise builds Common as a static library.
@@ -16,6 +20,31 @@ const commonFramework = ` installer.pod_targets.each do |pod|
16
20
  end`
17
21
 
18
22
  module.exports = function withAcsMeeting(config) {
23
+ config = withAppBuildGradle(config, (config) => {
24
+ if (config.modResults.language !== "groovy") {
25
+ throw new Error("@mentra/acs-meeting requires a Groovy app/build.gradle")
26
+ }
27
+ // A library cannot enable desugaring for its consuming app. Keep this
28
+ // requirement with ACS, including hosts that do not use the Crust plugin.
29
+ const tag = "acs-core-library-desugaring"
30
+ const contents = removeGeneratedContents(config.modResults.contents, tag) ?? config.modResults.contents
31
+ const desugaring = `android {
32
+ compileOptions {
33
+ coreLibraryDesugaringEnabled true
34
+ }
35
+ }
36
+ dependencies {
37
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
38
+ }`
39
+ config.modResults.contents = [
40
+ contents.trimEnd(),
41
+ createGeneratedHeaderComment(desugaring, tag, "//"),
42
+ desugaring,
43
+ `// @generated end ${tag}`,
44
+ "",
45
+ ].join("\n")
46
+ return config
47
+ })
19
48
  return withPodfile(config, (config) => {
20
49
  let contents = config.modResults.contents
21
50
  const hook = /^\s*pre_install do \|installer\|\s*$/m
@@ -5,70 +5,70 @@ import Foundation
5
5
  /// Fresh Swift sender on ACS iOS RawOutgoingVideoStream + CVPixelBuffer.
6
6
  /// No RealWear code (they have no iOS equivalent).
7
7
  final class AcsFrameSender: NSObject {
8
- private var stream: VirtualOutgoingVideoStream?
9
- private var running = false
10
- private var lastSent: CFTimeInterval = 0
11
- private let sendGate = VideoSendGate()
12
- private let stateLock = NSLock()
8
+ private var stream: VirtualOutgoingVideoStream?
9
+ private var running = false
10
+ private var lastSent: CFTimeInterval = 0
11
+ private let sendGate = VideoSendGate()
12
+ private let stateLock = NSLock()
13
13
 
14
- func attach(_ stream: VirtualOutgoingVideoStream) {
15
- detach()
16
- stateLock.lock()
17
- self.stream = stream
18
- lastSent = 0
19
- stateLock.unlock()
20
- stream.delegate = self
21
- }
22
-
23
- func send(_ pixelBuffer: CVPixelBuffer) {
24
- stateLock.lock()
25
- guard running, let stream else {
26
- stateLock.unlock()
27
- return
28
- }
29
- let fps = stream.format.framesPerSecond
30
- let now = CFAbsoluteTimeGetCurrent()
31
- if lastSent > 0, now - lastSent < 1.0 / Double(max(fps, 1)) {
32
- stateLock.unlock()
33
- return
34
- }
35
- guard let token = sendGate.acquire() else {
36
- stateLock.unlock()
37
- return
14
+ func attach(_ stream: VirtualOutgoingVideoStream) {
15
+ detach()
16
+ stateLock.lock()
17
+ self.stream = stream
18
+ lastSent = 0
19
+ stateLock.unlock()
20
+ stream.delegate = self
38
21
  }
39
- lastSent = now
40
- stateLock.unlock()
41
- let frame = RawVideoFrameBuffer()
42
- frame.buffer = pixelBuffer
43
- frame.streamFormat = stream.format
44
- frame.timestampInTicks = stream.timestampInTicks
45
- stream.send(frame: frame) { [sendGate] error in
46
- // Keep the source pixels alive until ACS finishes reading the frame.
47
- withExtendedLifetime(pixelBuffer) { frame.dispose() }
48
- sendGate.release(token)
49
- if let error { NSLog("ACS-SPIKE send frame failed: \(error)") }
22
+
23
+ func send(_ pixelBuffer: CVPixelBuffer) {
24
+ stateLock.lock()
25
+ guard running, let stream else {
26
+ stateLock.unlock()
27
+ return
28
+ }
29
+ let fps = stream.format.framesPerSecond
30
+ let now = CFAbsoluteTimeGetCurrent()
31
+ if lastSent > 0, now - lastSent < 1.0 / Double(max(fps, 1)) {
32
+ stateLock.unlock()
33
+ return
34
+ }
35
+ guard let token = sendGate.acquire() else {
36
+ stateLock.unlock()
37
+ return
38
+ }
39
+ lastSent = now
40
+ stateLock.unlock()
41
+ let frame = RawVideoFrameBuffer()
42
+ frame.buffer = pixelBuffer
43
+ frame.streamFormat = stream.format
44
+ frame.timestampInTicks = stream.timestampInTicks
45
+ stream.send(frame: frame) { [sendGate] error in
46
+ // Keep the source pixels alive until ACS finishes reading the frame.
47
+ withExtendedLifetime(pixelBuffer) { frame.dispose() }
48
+ sendGate.release(token)
49
+ if let error { NSLog("ACS-SPIKE send frame failed: \(error)") }
50
+ }
50
51
  }
51
- }
52
52
 
53
- func detach() {
54
- stateLock.lock()
55
- running = false
56
- // Drop the delegate on the previous stream so a late STOPPED from a prior
57
- // call cannot freeze outgoing video for the current meeting.
58
- let previous = stream
59
- stream = nil
60
- sendGate.reset()
61
- stateLock.unlock()
62
- previous?.delegate = nil
63
- }
53
+ func detach() {
54
+ stateLock.lock()
55
+ running = false
56
+ // Drop the delegate on the previous stream so a late STOPPED from a prior
57
+ // call cannot freeze outgoing video for the current meeting.
58
+ let previous = stream
59
+ stream = nil
60
+ sendGate.reset()
61
+ stateLock.unlock()
62
+ previous?.delegate = nil
63
+ }
64
64
  }
65
65
 
66
66
  extension AcsFrameSender: VirtualOutgoingVideoStreamDelegate {
67
- func virtualOutgoingVideoStream(_ virtualOutgoingVideoStream: VirtualOutgoingVideoStream, didChangeState _: VideoStreamStateChangedEventArgs) {
68
- stateLock.lock()
69
- defer { stateLock.unlock() }
70
- guard virtualOutgoingVideoStream === stream else { return }
71
- running = virtualOutgoingVideoStream.state == .started
72
- NSLog("ACS-SPIKE iOS raw video state=\(virtualOutgoingVideoStream.state)")
73
- }
67
+ func virtualOutgoingVideoStream(_ virtualOutgoingVideoStream: VirtualOutgoingVideoStream, didChangeState _: VideoStreamStateChangedEventArgs) {
68
+ stateLock.lock()
69
+ defer { stateLock.unlock() }
70
+ guard virtualOutgoingVideoStream === stream else { return }
71
+ running = virtualOutgoingVideoStream.state == .started
72
+ NSLog("ACS-SPIKE iOS raw video state=\(virtualOutgoingVideoStream.state)")
73
+ }
74
74
  }
@@ -22,6 +22,66 @@ Pod::Spec.new do |s|
22
22
  s.frameworks = 'AVFoundation', 'AudioToolbox', 'CoreMedia', 'CoreVideo', 'UIKit'
23
23
  s.pod_target_xcconfig = {
24
24
  'DEFINES_MODULE' => 'YES',
25
+ # Calling's umbrella does #import <AzureCommunicationCommon/AzureCommunicationCommon-Swift.h>.
26
+ # Vendored Common headers must resolve inside their framework module.
27
+ # Source-built pods retain the public-header compatibility fallback, and
28
+ # only the static-lib fallback needs a header-only framework.
29
+ 'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "${PODS_XCFRAMEWORKS_BUILD_DIR}/AzureCommunicationCommon" "${PODS_CONFIGURATION_BUILD_DIR}/AzureCommunicationCommon" "${PODS_CONFIGURATION_BUILD_DIR}/AcsMeeting"',
30
+ 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_CONFIGURATION_BUILD_DIR}" "${PODS_CONFIGURATION_BUILD_DIR}/AzureCommunicationCommon"',
25
31
  }
32
+ s.script_phases = [
33
+ {
34
+ :name => 'Expose AzureCommunicationCommon Swift header',
35
+ :execution_position => :before_compile,
36
+ :script => %(
37
+ set -e
38
+ # CocoaPods stages the slice for the current platform/architecture here.
39
+ # Do not pick a device or simulator slice directly from the pod sources.
40
+ XC_HDR="${PODS_XCFRAMEWORKS_BUILD_DIR}/AzureCommunicationCommon/AzureCommunicationCommon.framework/Headers/AzureCommunicationCommon-Swift.h"
41
+ FW_HDR="${PODS_CONFIGURATION_BUILD_DIR}/AzureCommunicationCommon/AzureCommunicationCommon.framework/Headers/AzureCommunicationCommon-Swift.h"
42
+ STATIC_HDR="${PODS_CONFIGURATION_BUILD_DIR}/AzureCommunicationCommon/Swift Compatibility Header/AzureCommunicationCommon-Swift.h"
43
+ if [ -f "$XC_HDR" ]; then
44
+ SRC="$XC_HDR"
45
+ elif [ -f "$FW_HDR" ]; then
46
+ SRC="$FW_HDR"
47
+ elif [ -f "$STATIC_HDR" ]; then
48
+ SRC="$STATIC_HDR"
49
+ else
50
+ echo "error: AzureCommunicationCommon-Swift.h missing (Common must build first)"
51
+ echo "looked for: $XC_HDR"
52
+ echo "looked for: $FW_HDR"
53
+ echo "looked for: $STATIC_HDR"
54
+ exit 1
55
+ fi
56
+ # This phase runs on every build, so only write when the bytes actually
57
+ # differ. An unconditional cp refreshes the mtime of a public header and
58
+ # makes Xcode recompile every dependent target for no reason.
59
+ copy_if_changed() {
60
+ if [ ! -f "$2" ] || ! cmp -s "$1" "$2"; then
61
+ mkdir -p "$(dirname "$2")"
62
+ cp -f "$1" "$2"
63
+ fi
64
+ }
65
+ # Never write a fake AzureCommunicationCommon.framework into BUILT_PRODUCTS_DIR.
66
+ # CocoaPods' embed script prefers ${BUILT_PRODUCTS_DIR}/$(basename) and would
67
+ # then codesign an empty header-only bundle ("bundle format unrecognized").
68
+ rm -rf "${PODS_CONFIGURATION_BUILD_DIR}/AzureCommunicationCommon.framework"
69
+ PUBLIC_HDR="${PODS_ROOT}/Headers/Public/AzureCommunicationCommon/AzureCommunicationCommon-Swift.h"
70
+ if [ -f "$XC_HDR" ]; then
71
+ # A loose copy shadows the vendored framework's modular header. Clang
72
+ # then imports its credential into Calling as a second Swift type,
73
+ # incompatible with Common.CommunicationTokenCredential. Remove copies
74
+ # left by earlier builds and let the framework search path resolve it.
75
+ rm -f "$PUBLIC_HDR"
76
+ else
77
+ copy_if_changed "$SRC" "$PUBLIC_HDR"
78
+ fi
79
+ if [ ! -f "$XC_HDR" ] && [ ! -f "$FW_HDR" ]; then
80
+ FAKE_HEADERS="${PODS_CONFIGURATION_BUILD_DIR}/AcsMeeting/AzureCommunicationCommon.framework/Headers"
81
+ copy_if_changed "$SRC" "$FAKE_HEADERS/AzureCommunicationCommon-Swift.h"
82
+ fi
83
+ ),
84
+ },
85
+ ]
26
86
  s.source_files = '*.{h,m,mm,swift}', 'PolicyKit/Sources/AcsAudioPolicy/*.swift'
27
87
  end