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
9 changes: 9 additions & 0 deletions src/OpenIPC.Viewer.Devices/Onvif/OnvifClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ private static async Task<DeviceClient> CreatePreAuthDeviceAsync(Uri uri)
{
shift = await probe.GetDeviceTimeShift().ConfigureAwait(false);
}
catch (CommunicationException)
{
// Some firmwares answer GetSystemDateAndTime with an empty/truncated
// body ("Unexpected end of file"), which used to abort the whole add
// flow. The shift only compensates camera clock skew for the
// WS-UsernameToken digest — assume zero and proceed; if the camera
// truly rejects the digest, the next call surfaces the real error.
shift = TimeSpan.Zero;
}
finally
{
CloseQuietly(probe);
Expand Down
12 changes: 12 additions & 0 deletions src/OpenIPC.Viewer.Video/OpenIPC.Viewer.Video.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,42 @@
the directory not existing, so contributors without a given RID's binaries
aren't blocked. Linux/macOS users can alternatively rely on system
apt/brew-installed FFmpeg — FfmpegRuntime falls back to the loader path.

ExcludeFromSingleFile keeps these out of the PublishSingleFile bundle.
Bundled (IncludeNativeLibrariesForSelfExtract) they extract to a temp dir
under their runtimes\<rid>\native\ subpath, which is NOT on the host's
native search path — and AppContext.BaseDirectory still points at the exe
dir, so FfmpegRuntime.ResolveNativeDir finds nothing and every av_* call
throws NotSupportedException. Loose files next to the exe also keep the
LGPL DLLs user-replaceable.
-->
<ItemGroup>
<Content Include="..\..\runtimes\win-x64\native\*.dll"
Condition="Exists('..\..\runtimes\win-x64\native')">
<Link>runtimes\win-x64\native\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<Visible>false</Visible>
</Content>
<Content Include="..\..\runtimes\linux-x64\native\*.so*"
Condition="Exists('..\..\runtimes\linux-x64\native')">
<Link>runtimes\linux-x64\native\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<Visible>false</Visible>
</Content>
<Content Include="..\..\runtimes\osx-arm64\native\*.dylib"
Condition="Exists('..\..\runtimes\osx-arm64\native')">
<Link>runtimes\osx-arm64\native\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<Visible>false</Visible>
</Content>
<Content Include="..\..\runtimes\osx-x64\native\*.dylib"
Condition="Exists('..\..\runtimes\osx-x64\native')">
<Link>runtimes\osx-x64\native\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<Visible>false</Visible>
</Content>
</ItemGroup>
Expand Down
21 changes: 19 additions & 2 deletions src/OpenIPC.Viewer.Video/Pipeline/FfmpegRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,21 @@ namespace OpenIPC.Viewer.Video.Pipeline;
internal static class FfmpegRuntime
{
private static int _initialized;
private static Exception? _initFailure;

public static void EnsureInitialized()
{
if (Interlocked.CompareExchange(ref _initialized, 1, 0) != 0)
{
// Replay a failed first init instead of returning success — otherwise
// every later session walks straight into uninitialized bindings and
// dies mid-stream with a bare "Specified method is not supported"
// (what the field logs showed: one descriptive crash, then an endless
// reconnect loop of cryptic av_dict_set failures).
if (_initFailure is not null)
throw _initFailure;
return;
}

if (OperatingSystem.IsAndroid())
{
Expand Down Expand Up @@ -54,13 +64,15 @@ public static void EnsureInitialized()
// message is preserved.
var (rid, _) = RuntimeIds.Current();
var probe = string.IsNullOrEmpty(nativeDir) ? "(loader path)" : nativeDir;
throw new FfmpegNativeLibsMissingException(
_initFailure = new FfmpegNativeLibsMissingException(
$"FFmpeg native libraries failed to load for runtime '{rid ?? "unknown"}'. " +
$"Probed: {probe}. " +
$"Android: ensure runtimes/android-{{arm64,x64}}/native/*.so are populated " +
$"(run tools/build-ffmpeg-android.sh via WSL or pull .so from a CI APK artifact). " +
$"Desktop: tools/fetch-ffmpeg.ps1 (Windows) or apt/brew. " +
$"Desktop: keep the runtimes/ folder from the release archive next to the exe, " +
$"or tools/fetch-ffmpeg.ps1 (Windows) / apt/brew. " +
$"Underlying: {DescribeChain(ex)}", ex);
throw _initFailure;
}
}

Expand All @@ -70,6 +82,11 @@ private static bool IsNativeLoadFailure(Exception ex)
{
if (cur is DllNotFoundException) return true;
if (cur is BadImageFormatException) return true;
// FFmpeg.AutoGen's DynamicallyLoadedBindings doesn't throw on a
// failed library load — it leaves the function vector pointing at a
// stub that throws NotSupportedException on first call. Within this
// init scope that's always a load failure, not a real capability gap.
if (cur is NotSupportedException) return true;
}
return ex is TypeInitializationException;
}
Expand Down
Loading