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
64 changes: 57 additions & 7 deletions media_kit/doc/native_assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ dependency and override. There are no replacement container packages.

This branch requires Dart 3.10 or newer and Flutter 3.38 or newer. Both packages
must come from the same checkout or migrated revision; previously published
versions do not implement this protocol. Kazumi uses adjacent local checkouts
until these changes are published.
versions do not implement this protocol. Git consumers should pin both package
paths (`media_kit` and `media_kit_video`) to the same immutable commit. Kazumi
uses the OHOS adaptation in the `tignioj/media-kit` fork this way.

## Ownership

Expand Down Expand Up @@ -92,11 +93,35 @@ it. Apple requires Xcode (including `lipo`, `otool`, `install_name_tool`, `codes
Windows 7z extraction uses the system `tar.exe`; other archives use ZIP or tar.
All native targets require a C compiler for the small lifecycle bridge.

An OHOS artifact is retained in the catalog, but **OHOS builds are not supported
by the upstream `code_assets`/C toolchain used here**. An OHOS Flutter/Dart port
must implement the hook target, compiler configuration and runtime asset mapping
before this branch can ship on OHOS. Keeping the former binary-only package would
not implement those requirements. The ArkTS video renderer remains in the tree.
OHOS arm64 is supported with a Flutter-OH SDK that implements Native Assets
(tested with 3.41.9+ohos-1.0.2, Dart 3.11.5). Flutter-OH currently reports a Linux
target to hosted hooks, so the hook identifies its OHOS compiler SDK, selects
`ohos_arm64`, and compiles the event-loop bridge with the OHOS target triple and
sysroot. It does not use Linux binaries. The catalog's primary filename matches
the archive and SONAME: `libmpv.so`.

The OHOS ArkTS renderer still supplies the Flutter texture and native surface.
The hook also bundles the SDK's shared C++ runtime required by libmpv (unless a
local bundle supplies it). Use a native SDK compatible with the consuming
application's minimum HarmonyOS API level.
Only arm64 has a pinned OHOS bundle; other OHOS architectures fail explicitly.
Emulators are not supported by the renderer; validate on a physical device.

The compiler supplied by Flutter-OH normally locates the SDK automatically.
If it is unavailable, configure the consuming application's OHOS builds only:

```yaml
hooks:
user_defines:
media_kit:
target: ohos
ohos-sdk: /absolute/path/to/openharmony/native
```

`OHOS_NDK_HOME` and `HOS_SDK_HOME` are also accepted for explicit OHOS targets.
Setting these environment variables alone does not redirect normal Linux builds.
The dependency ranges support Flutter-OH's pinned `meta 1.17` as well as newer
hook/code-assets versions, without overriding Flutter SDK dependencies.

Apple deployment minimums are also constrained by the selected Flutter SDK's
native-asset framework generator. Flutter 3.47.5 emits iOS 15 / macOS 13 minimums;
Expand Down Expand Up @@ -134,3 +159,28 @@ unchanged. Linux and Apple release archives were downloaded and SHA-256 verified
Apple framework metadata and architecture selections were inspected. Their
application builds and runtime behavior still require Linux/macOS hosts. No APK
was installed, overwritten or uninstalled on the connected Android device.


OHOS validation (2026-10-17): the hook/core suite passed 19 tests on macOS;
Kazumi passed 213 Flutter tests and built both smoke and normal Debug HAPs with
Flutter-OH 3.41.9+ohos-1.0.2. The HAP contains the OHOS libmpv, C event-loop
bridge, shared C++ runtime and their `ohos_arm64` Native Assets mappings.
On a MatePad mini (HarmonyOS API 26), two runs each completed 12 render/dispose
cycles across software/hardware decoding and synchronous/asynchronous player
modes, plus 3 disposals during initialization. Hardware decoding reported
`ohcodec`. Concurrent/repeated disposal completed, the native handle became
null, and disposal took 69–136 ms across both runs. A device screenshot confirmed
the test video was displayed through the Flutter texture. The normal-entry
Kazumi HAP was then installed and launched successfully. OHOS emulators and other OHOS architectures were not validated.


Release AOT follow-up (2026-10-17): after installing Rosetta for Flutter-OH's
x86_64 macOS snapshot compiler, both Kazumi smoke and normal-entry Release HAPs
built and passed CLI signing/verification. Both contain arm64 `libapp.so`, have
`debug=false`, and omit `kernel_blob.bin`. On the same MatePad mini, two Release
runs each passed 12 render/dispose cycles and 3 disposals during initialization
(24 and 6 total). Software (`no`) and OHOS hardware (`ohcodec`) decoding worked
in synchronous and asynchronous modes; concurrent/repeated disposal completed
with a null native handle in 76–160 ms. A device screenshot confirmed actual
hardware-decoded texture rendering. The normal-entry Release HAP was then
installed in place of the smoke build.
22 changes: 20 additions & 2 deletions media_kit/hook/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,34 @@ import 'package:path/path.dart' as p;

import 'src/apple.dart';
import 'src/bundles.dart';
import 'src/ohos.dart';

void main(List<String> arguments) async {
await build(arguments, (input, output) async {
if (!input.config.buildCodeAssets) return;
final os = input.config.code.targetOS;
final architecture = input.config.code.targetArchitecture;
final ohos = OhosBuildConfig.detect(
targetOS: os,
architecture: architecture,
compiler: input.config.code.cCompiler?.compiler,
sdkPath: input.userDefines.path('ohos-sdk')?.toFilePath(),
explicitlyOhos: input.userDefines['target'] == 'ohos',
);
final catalogFile = File.fromUri(
input.packageRoot.resolve('hook/native_bundles.json'),
);
final catalog = await Bundle.read(catalogFile);
output.dependencies.add(catalogFile.uri);
final target = '${os.name}_${architecture.name}';
final target = '${ohos == null ? os.name : 'ohos'}_${architecture.name}';
final bundle = catalog[target];
if (bundle == null) {
throw UnsupportedError('No media-kit native bundle for $target');
}

final source = input.userDefines['source'] ?? 'bundled';
if (source == 'system') {
if (os != OS.linux && os != OS.windows) {
if (ohos != null || (os != OS.linux && os != OS.windows)) {
throw UnsupportedError(
'System libmpv is supported on Linux and Windows only',
);
Expand Down Expand Up @@ -77,6 +85,12 @@ void main(List<String> arguments) async {
input.config.code.iOS.targetSdk == IOSSdk.iPhoneSimulator,
)
: await selectLibraries(directory, bundle.library);
if (ohos != null &&
!libraries.any(
(file) => p.basename(file.path) == 'libc++_shared.so',
)) {
libraries.add(ohos.sharedCxxRuntime);
}
if (os == OS.android &&
!libraries.any(
(file) => p.basename(file.path) == 'libmediakitandroidhelper.so',
Expand All @@ -98,6 +112,10 @@ void main(List<String> arguments) async {
output.dependencies.add(library.uri);
}
}
if (ohos != null) {
await ohos.buildBridge(input, output);
return;
}
await CBuilder.library(
name: 'media_kit_event_loop',
assetName: 'src/player/native/core/native_event_loop_bindings.dart',
Expand Down
2 changes: 1 addition & 1 deletion media_kit/hook/native_bundles.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"url": "https://github.com/ErBWs/libmpv-ohos-build/releases/download/20260811/libmpv_aarch64.zip",
"sha256": "2bfb9844a7552c450694581b315b26cdf6202eaa57724e9f70f3facfad75163b",
"format": "zip",
"library": "libmpv.so.2"
"library": "libmpv.so"
}
}
}
130 changes: 130 additions & 0 deletions media_kit/hook/src/ohos.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'dart:io';

import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:path/path.dart' as p;

import 'process.dart';

/// Flutter-OH currently reports Linux with an OHOS compiler to hosted hooks.
/// Identify the target SDK instead of assuming that Linux binaries will work.
final class OhosBuildConfig {
OhosBuildConfig._(this.nativeSdk);

final String nativeSdk;

File get sharedCxxRuntime {
final file = File(
p.join(
nativeSdk,
'llvm',
'lib',
'aarch64-linux-ohos',
'libc++_shared.so',
),
);
if (!file.existsSync()) {
throw StateError('OHOS libmpv requires the SDK runtime: ${file.path}');
}
return file;
}

static OhosBuildConfig? detect({
required OS targetOS,
required Architecture architecture,
Uri? compiler,
String? sdkPath,
bool explicitlyOhos = false,
Map<String, String>? environment,
}) {
if (explicitlyOhos && targetOS != OS.linux && targetOS.name != 'ohos') {
throw UnsupportedError('An explicit OHOS target requires Flutter-OH');
}
final compilerSdk = compiler == null
? null
: _findNativeSdk(
p.dirname(p.dirname(p.dirname(compiler.toFilePath()))),
);
if (!explicitlyOhos &&
targetOS.name != 'ohos' &&
!(targetOS == OS.linux && compilerSdk != null)) {
return null;
}
if (architecture != Architecture.arm64) {
throw UnsupportedError('media-kit currently bundles OHOS arm64 only');
}
final env = environment ?? Platform.environment;
for (final candidate in [
?sdkPath,
?compilerSdk,
?env['OHOS_NDK_HOME'],
?env['HOS_SDK_HOME'],
]) {
final sdk = _findNativeSdk(candidate);
if (sdk != null) return OhosBuildConfig._(sdk);
}
throw StateError(
'OpenHarmony native SDK not found. Set OHOS_NDK_HOME or '
'hooks.user_defines.media_kit.ohos-sdk to the native SDK directory.',
);
}

static String? _findNativeSdk(String root) {
for (final candidate in [
root,
p.join(root, 'native'),
p.join(root, 'openharmony', 'native'),
p.join(root, 'default', 'openharmony', 'native'),
]) {
if (File(
p.join(candidate, 'build', 'cmake', 'ohos.toolchain.cmake'),
).existsSync()) {
return p.normalize(p.absolute(candidate));
}
}
return null;
}

/// CBuilder's Linux target supplies a GNU triple; compile this bridge with
/// the OHOS triple and sysroot while keeping Flutter-OH's asset protocol.
Future<void> buildBridge(BuildInput input, BuildOutputBuilder output) async {
final suffix = Platform.isWindows ? '.exe' : '';
final include = File(
Platform.resolvedExecutable,
).parent.parent.uri.resolve('include/');
final sources = [
'src/native_event_loop.c',
'src/native_library.c',
].map((source) => input.packageRoot.resolve(source)).toList();
final library = input.outputDirectory.resolve('libmedia_kit_event_loop.so');
await Directory.fromUri(input.outputDirectory).create(recursive: true);
await runNativeTool(p.join(nativeSdk, 'llvm', 'bin', 'clang$suffix'), [
'--target=aarch64-linux-ohos',
'--sysroot=${p.join(nativeSdk, 'sysroot')}',
'-shared',
'-fPIC',
'-O2',
'-Wl,-z,defs',
'-Wl,-soname,libmedia_kit_event_loop.so',
'-I${include.toFilePath()}',
...sources.map((source) => source.toFilePath()),
'-o',
library.toFilePath(),
'-ldl',
]);
output.assets.code.add(
CodeAsset(
package: input.packageName,
name: 'src/player/native/core/native_event_loop_bindings.dart',
linkMode: DynamicLoadingBundled(),
file: library,
),
);
output.dependencies.addAll([
...sources,
include.resolve('dart_native_api.h'),
include.resolve('dart_api.h'),
File(p.join(nativeSdk, 'oh-uni-package.json')).uri,
]);
}
}
7 changes: 4 additions & 3 deletions media_kit/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ dependencies:
archive: ^4.0.0
crypto: ^3.0.3
xml: ^6.5.0
code_assets: ^2.1.0
hooks: ^2.2.0
native_toolchain_c: ^0.19.5
# Flutter-OH 3.41 pins meta 1.17. These hooks also support that toolchain.
code_assets: '>=1.2.1 <3.0.0'
hooks: '>=2.0.2 <3.0.0'
native_toolchain_c: '>=0.19.1 <0.20.0'
collection: ^1.17.0
http: ">=0.13.0 <2.0.0"
image: ^4.0.17
Expand Down
2 changes: 2 additions & 0 deletions media_kit/test/hook/native_bundles_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ void main() {
'linux_x64',
'ios_arm64',
'macos_arm64',
'ohos_arm64',
]),
);
for (final bundle in catalog.values) {
expect(bundle.url.scheme, 'https');
expect(bundle.checksum, matches(r'^[a-f0-9]{64}$'));
}
expect(catalog['ohos_arm64']!.library, 'libmpv.so');
});

test('rejects traversal, absolute and drive archive paths', () {
Expand Down
Loading
Loading