Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,61 @@ jobs:
if-no-files-found: ignore
retention-days: 7

# Adds the -windows target frameworks to the packages the macOS pack job produced.
#
# OpenTok.Net has three platform heads and no runner can build all of them: the ios head needs
# Xcode, the windows head needs the Windows SDK reference packs and WinUI. So
# src/OpenTok.Net.props appends the -windows target frameworks only when building on Windows,
# which makes the macOS-packed package correct, complete for iOS and Android, and silently devoid
# of Windows assets.
#
# This closes that the same way the net9/net10 band split is closed — pack again where it can be
# packed, then merge with build/merge-packages.py. Uploaded under a second artifact name rather
# than replacing the first, because an artifact cannot be overwritten and because the two are
# genuinely different: the release job publishes this one, while the iOS and Android verification
# jobs are unaffected by whether it succeeded.
add-windows-assets:
name: add Windows assets to the packages
timeout-minutes: 30
needs: pack
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
9.0.x
10.0.x

- name: Install the MAUI workload
run: dotnet workload install maui-windows

- name: Download packages
uses: actions/download-artifact@v4
with:
name: nuget-packages
path: artifacts

# The script asserts the result itself: require_windows_frameworks reads the lib/<tfm>/ folders
# out of every merged package and fails the step if any of them carries no Windows assets, so
# a pack that quietly emitted no -windows target framework cannot reach the upload below.
# Repeating that here as a shell one-liner was worse than nothing: with `-o pipefail`, which
# this shell sets, `unzip -l pkg | grep -q ...` reports failure whenever grep matches early
# enough to exit before unzip has finished writing, because unzip then dies of SIGPIPE and its
# 141 becomes the pipeline's status. That is a coin toss on the package's entry order, and it
# came up tails on a package that did have its Windows assets.
- name: Pack the Windows heads and merge them in
shell: bash
run: ./build/AddWindowsAssets.sh "${{ inputs.version }}"

- name: Upload packages
uses: actions/upload-artifact@v4
with:
name: nuget-packages-with-windows
path: artifacts

sample:
name: build sample app
timeout-minutes: 45
Expand Down Expand Up @@ -354,7 +409,10 @@ jobs:
verify-release:
name: Release build (${{ matrix.platform }})
timeout-minutes: 45
needs: pack
# add-windows-assets as well as pack: only the windows leg needs it, but a matrix has one set
# of dependencies. The cost is that the iOS and Android legs wait for a Windows pack they do not
# use; the alternative is a second nearly identical job.
needs: [pack, add-windows-assets]
if: ${{ inputs.verify-release }}
runs-on: ${{ matrix.runs-on }}
strategy:
Expand All @@ -374,6 +432,19 @@ jobs:
target-framework: net9.0-android35.0
workload: maui-android
extra-args: -p:RuntimeIdentifier=android-arm64
# x64, not the runner's native architecture: OpenTok.Client's native payload has no arm64
# build, and OpenTok.Net.Win's own targets file fails the build rather than let that
# surface at runtime. Also the only leg that needs the Windows assets to have been merged
# into the package — see the add-windows-assets job.
- platform: windows
runs-on: windows-latest
target-framework: net9.0-windows10.0.19041.0
workload: maui-windows
# OpenTokWindowsOnly, so the sample declares the Windows head alone. Without it the
# declared list still names an ios head this runner cannot build in principle, which
# breaks MauiVersion resolution for the head it can — the NU1015 trap the sample's own
# .csproj documents.
extra-args: -p:OpenTokWindowsOnly=true -p:RuntimeIdentifier=win-x64 -p:SelfContained=false
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -404,10 +475,12 @@ jobs:
with:
packages: 'platforms;android-35'

# The Windows leg needs the package that has Windows assets in it, which is a different
# artifact — see the add-windows-assets job for why there are two.
- name: Download packages
uses: actions/download-artifact@v4
with:
name: nuget-packages
name: ${{ matrix.platform == 'windows' && 'nuget-packages-with-windows' || 'nuget-packages' }}
path: artifacts

# net9, not net10 as the Debug sample job uses: this needs no scratch global.json, and the
Expand All @@ -429,7 +502,12 @@ jobs:
#
# Unlike the Debug sample job, this cannot be fixed by installing both workloads: that job
# runs entirely on macOS, where both exist. This one deliberately builds Android on Linux.
# shell: bash on this step and the next, because the windows leg would otherwise get pwsh —
# which reads the line continuations below as an operator and fails to parse the command
# before ever running it. The two legs on macOS and Linux already had bash by default, which
# is why this only ever surfaced here.
- name: Build the sample in Release
shell: bash
run: |
dotnet build samples/OpenTok.Sample.Maui/OpenTok.Sample.Maui.csproj \
--configuration Release \
Expand All @@ -441,6 +519,7 @@ jobs:
# .app is assembled, and on Android r8 and the packaging step run before the APK is. A failure
# in any of them never gets far enough to produce one.
- name: Check the app was produced
shell: bash
run: |
case "${{ matrix.platform }}" in
ios)
Expand All @@ -449,6 +528,9 @@ jobs:
android)
produced="$(find samples/OpenTok.Sample.Maui/bin/Release -name '*.apk' -print -quit)"
;;
windows)
produced="$(find samples/OpenTok.Sample.Maui/bin/Release -name 'OpenTok.Sample.Maui.exe' -print -quit)"
;;
esac

if [ -z "${produced}" ]; then
Expand All @@ -457,3 +539,18 @@ jobs:
fi

echo "produced ${produced}"

# The renderer is useless if Vonage's native payload does not travel with it, and unlike the
# other two platforms nothing about the build fails when it does not — opentok.dll is copied
# by a plain Content glob in OpenTok.Client's own targets, so a packaging change drops it
# silently and the app dies on the first call into the SDK.
- name: Check the native payload reached the output
if: matrix.platform == 'windows'
shell: bash
run: |
found="$(find samples/OpenTok.Sample.Maui/bin/Release -name 'opentok.dll' -print -quit)"
if [ -z "${found}" ]; then
echo "::error::opentok.dll is missing from the Windows build output"
exit 1
fi
echo "ok ${found}"
8 changes: 7 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,16 @@ jobs:
with:
dotnet-version: 9.0.x

# nuget-packages-with-windows, not nuget-packages. The pack job runs on macOS and cannot build
# a -windows target framework at all, so its output is complete for iOS and Android and has no
# Windows assets whatsoever; build.yml's add-windows-assets job packs those separately and
# merges them in. Publishing the wrong one of the two would ship a package that restores fine
# and then has nothing for a Windows head — silently, since NuGet reports a missing target
# framework only when something asks for it.
- name: Download packages
uses: actions/download-artifact@v4
with:
name: nuget-packages
name: nuget-packages-with-windows
path: artifacts

# nuget.org is published to first: a GitHub release linking to packages that failed to upload
Expand Down
46 changes: 45 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
makes the package invisible to `dotnet add package`.
-->
<OpenTokVersion>2.34.1</OpenTokVersion>
<OpenTokBindingRevision>3</OpenTokBindingRevision>
<OpenTokBindingRevision>4</OpenTokBindingRevision>
<VersionPrefix>$(OpenTokVersion).$(OpenTokBindingRevision)</VersionPrefix>

<!--
Expand Down Expand Up @@ -62,6 +62,50 @@
<OpenTokSupportedIosVersion>15.0</OpenTokSupportedIosVersion>
<OpenTokSupportedAndroidVersion>24</OpenTokSupportedAndroidVersion>

<!--
Windows, where two different numbers are needed and conflating them is the usual mistake.

OpenTokWindowsSdkVersion (10.0.19041.0) is what the target framework is *built* against — the
Windows App SDK's floor for WinUI 3, and therefore not negotiable.

OpenTokSupportedWindowsVersion (10.0.17763.0) is where the result will *run*, and is .NET
MAUI's own floor. Setting the runtime floor to 19041 as well would refuse to install on
machines MAUI itself supports, for no gain.
-->
<OpenTokWindowsSdkVersion>10.0.19041.0</OpenTokWindowsSdkVersion>
<OpenTokSupportedWindowsVersion>10.0.17763.0</OpenTokSupportedWindowsVersion>

<!--
The Windows App SDK, which is what provides WinUI 3. Must match what OpenTok.Net.Win was built
against — both end up in one app, and two WinUI runtimes is not a thing that resolves quietly.
-->
<WindowsAppSdkVersion>2.3.1</WindowsAppSdkVersion>

<!--
The Windows head's own package. Exact, like the other two — see the comment on those, and note
the extra reason here: OpenTok.Net.Win carries the renderer that reads VideoFrame planes
directly, so a mismatched pair is not a version-skew inconvenience but a memory-safety one.

Because the pin is exact, the Windows jobs here cannot run until this version exists on
nuget.org — restore fails with NU1102 and the whole Windows leg goes red for a reason that has
nothing to do with the change under review. While a Windows change is still in flight, point
this at the beta that repository's own pull request publishes:

<OpenTokWinPackageVersion>2.34.1.4-beta.7.3</OpenTokWinPackageVersion>

OpenTok.Net.Win's pr.yml pushes one per pull-request build and prints the exact version in the
job summary. Move it back to the stable version before tagging a release here — the release
notes for 2.34.1.4 spell out that ordering, and OpenTok.Net 2.34.1.2 is what happens when it
is not followed.
-->
<OpenTokWinPackageVersion>2.34.1.4</OpenTokWinPackageVersion>

<!--
Vonage's own Windows SDK. Arrives transitively through OpenTok.Net.Win anyway; pinned here
because this façade compiles against OpenTok.Session and OpenTok.Publisher directly.
-->
<OpenTokClientPackageVersion>2.34.1</OpenTokClientPackageVersion>

<Authors>s.bokatuk</Authors>
<Company>s.bokatuk</Company>
<Copyright>Copyright © s.bokatuk</Copyright>
Expand Down
39 changes: 32 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
[![OpenTok SDK 2.34.1](https://img.shields.io/badge/OpenTok%20SDK-2.34.1-099DFD)](https://developer.vonage.com/en/video/client-sdk/overview)
[![Licence: MIT](https://img.shields.io/badge/licence-MIT-green)](LICENSE)

One `Session`/`Publisher`/`Subscriber` API over Vonage's (formerly TokBox's) native OpenTok iOS and
Android SDKs, so a .NET or .NET MAUI app writes its calling code once.
One `Session`/`Publisher`/`Subscriber` API over Vonage's (formerly TokBox's) OpenTok iOS, Android
and Windows SDKs, so a .NET or .NET MAUI app writes its calling code once.

```bash
dotnet add package OpenTok.Net.Maui
Expand All @@ -27,8 +27,8 @@ session.StreamReceived += (_, e) =>
session.Connect(token);
```

No `#if IOS`, no `#if ANDROID`, no delegate subclass, no Java listener, no `JavaCast`, and no
hand-written video-view handler. `samples/OpenTok.Sample.Maui` is that code as a running app —
No `#if IOS`, no `#if ANDROID`, no `#if WINDOWS`, no delegate subclass, no Java listener, no
`JavaCast`, and no hand-written video-view handler. `samples/OpenTok.Sample.Maui` is that code as a running app —
compare it with the per-platform samples in the two binding repositories, which are the same flow
written twice.

Expand All @@ -38,10 +38,35 @@ written twice.

| Package | Depends on | Use it when |
| --- | --- | --- |
| `OpenTok.Net` | [`OpenTok.Net.iOS`](https://github.com/sbokatuk/OpenTok.Net.iOS) / [`OpenTok.Net.Android`](https://github.com/sbokatuk/OpenTok.Net.Android), per target framework | Always — `OpenTokSession`, `OpenTokPublisher`, `OpenTokSubscriber`. |
| `OpenTok.Net` | [`OpenTok.Net.iOS`](https://github.com/sbokatuk/OpenTok.Net.iOS) / [`OpenTok.Net.Android`](https://github.com/sbokatuk/OpenTok.Net.Android) / [`OpenTok.Net.Win`](https://github.com/sbokatuk/OpenTok.Net.Win), per target framework | Always — `OpenTokSession`, `OpenTokPublisher`, `OpenTokSubscriber`. |
| `OpenTok.Net.Maui` | `OpenTok.Net` + `Microsoft.Maui.Controls` | You are building MAUI and want `OpenTokVideoView` rather than writing a handler. |

Both carry `net8.0`, `net9.0` and `net10.0` for iOS and Android — six target frameworks each.
Both carry `net8.0`, `net9.0` and `net10.0` for iOS, Android and Windows — nine target frameworks
each.

### Windows

Windows arrives differently from the other two. Vonage's `OpenTok.Client` is already managed .NET,
so there is no binding to generate — but it ships video renderers only for WPF and Windows Forms,
and none at all on the `netstandard2.0` asset a modern .NET app resolves. .NET MAUI on Windows is
WinUI 3, so without help a MAUI app can connect, publish and subscribe and have nowhere to put the
picture. [`OpenTok.Net.Win`](https://github.com/sbokatuk/OpenTok.Net.Win) 2.34.1.4 supplies the WinUI
renderer; this façade uses it, and you do not reference it directly.

Two things about Windows that the other platforms do not ask of you:

* **x64 only.** `OpenTok.Client`'s native payload has no arm64 build. `OpenTok.Net.Win` fails the
build with **OTW0001** rather than letting that become a `BadImageFormatException` after launch.
Windows on ARM runs the x64 build under emulation.
* **Create the first OpenTok object on the UI thread.** The Windows SDK has an explicit context
object with no iOS or Android equivalent, and the façade binds it to that thread's dispatcher
queue so events arrive somewhere they can touch the UI. It is what `OpenTokSession` already asks
for; on Windows it is enforced rather than advised.

Some of the shared API has no Windows equivalent and is a documented no-op there rather than an
exception — camera position, torch and zoom (desktop webcams have none), the end-to-end encryption
secret, `Pause()`/`Resume()`, and all of `OpenTokAudioSession` (Windows has no CallKit analogue). A
façade whose common API throws on one platform would not be a façade.

For background blur, background replacement or noise suppression, add the platform transformers
package too — `OpenTok.Net.Transformers.iOS` and/or `OpenTok.Net.Transformers.Android`. Neither is
Expand Down Expand Up @@ -76,7 +101,7 @@ per-platform code.

| Sample | Platforms | Shows |
| --- | --- | --- |
| `OpenTok.Sample.Maui` | both | Everything in the table above, one page, no per-platform code |
| `OpenTok.Sample.Maui` | iOS, Android, Windows | Everything in the table above, one page, no per-platform code |
| `OpenTok.Sample.CallKit.iOS` | iOS | `CXProvider`, answering from the system call UI, audio handed to CallKit |
| `OpenTok.Sample.Telecom.Android` | Android | `ConnectionService` + `PhoneAccount`, and a camera/microphone foreground service |

Expand Down
Loading
Loading