Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nativephp Media Laravel Package

protendai/nativephp-media

NativePHP Media brings image, audio, and video handling to your NativePHP desktop apps: pick and store files, generate thumbnails and conversions, and manage media libraries with a simple, Laravel-style API.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish config

composer require protendai/nativephp-media
php artisan vendor:publish --tag=nativephp-media-config

Then add the JS side if you're building a hybrid UI:

npm install @protendai/nativephp-media

Where to look first

  1. config/nativephp-media.php — two keys only, but both matter: library and usage_descriptions.
  2. Media:: facade (PHP) and @protendai/nativephp-media (JS). The JS package exposes one function per bridge function, the event constants, and TypeScript types — if you're writing a player UI, start there and mirror the event names rather than string-typing them.
  3. The bundled Boost guidelines — they're the fastest way to get an agent (or yourself) producing correct bridge calls.

First use case — play your own file or a URL

This is the path of least resistance and the one the v1.0.0 defaults are optimised for:

Media::setQueue([
    ['id' => 'ep1', 'url' => asset('audio/ep1.mp3'), 'title' => 'Episode 1'],
]);

Because device library access is opt-in, this works with no Android READ_MEDIA_* declarations and no iOS Music/Photos usage strings. You don't need a Google Play photo/video permission declaration either. Only set NATIVEPHP_MEDIA_LIBRARY when you actually browse the user's music/video library.

For video, Media::openVideo($item) — but note the resume semantics below before you wire up "continue watching".

Implementation Patterns

Gate library access behind an explicit env flag

# unset / false  -> own files and URLs only (recommended default)
# true | audio | video -> declares the matching READ_MEDIA_* + iOS usage strings
NATIVEPHP_MEDIA_LIBRARY=audio

Treat this as a build-time product decision, not a runtime toggle. Turning it on changes your store permission declarations, so it belongs in the same conversation as your Play Store listing.

Resume vs. restart — the startPosition contract

The release unified resume behaviour across platforms, and it's worth encoding in your app logic:

Call Behaviour
setQueue(...) / openVideo(..., startPosition: -1) Resumes the item's saved position
skipTo(...) Resumes the saved position and (new on Android) starts playback
next() / previous() / auto-advance Always starts from 0

So a "Continue listening" button is setQueue + startPosition = -1; a "Play from start" button is the same call with startPosition = 0. Don't try to emulate resume by seeking after the fact — you'll fight the position-saving logic that now correctly keys positions to the outgoing item.

Permissions: handle three states, not two

$result = Media::permissionStatus();

if ($result->granted && $result->limited) {
    // iOS "Selected Photos" / Android 14 "Select videos":
    // you can read what's there, but you cannot assume completeness.
} elseif ($result->granted) {
    // full library
} else {
    // denied — and note: requesting without a usage string now returns
    // granted: false instead of terminating the app
}

Design any library browser to degrade gracefully under limited: show what you get, offer a "manage selection" affordance, never imply the list is exhaustive. Re-query permissionStatus() after returning from system settings.

Keep permissionStatus() non-blocking in the UI

A request lost to activity recreation is now always answered with a PermissionResult reflecting current state — but it can arrive asynchronously. Drive your UI from the event/result, not from an optimistic in-memory flag set at request time.

Android playback resumption

If you want the system resume card and media buttons to bring the last queue back, implement onPlaybackResumption and make sure MediaButtonReceiver is wired. The queue you pass there should be the same shape as setQueue, so keep a serialisable copy of it (ids, URLs, metadata) — don't rebuild it from ephemeral state that's gone after process death.

Testing with FakeBridge

FakeBridge::withMedia();

Media::setQueue($items);
FakeBridge::assertMediaQueued($items);

Media::openVideo($item);
FakeBridge::assertVideoOpened($item);

Media::permissionStatus();
FakeBridge::assertMediaPermissionRequested();

This is the single biggest day-to-day quality-of-life addition: you can now test player controllers and Livewire components without a device. Wrap bridge-touching code in a small service so tests can target it directly.

Overriding iOS usage strings

Your app's own nativephp.permissions value always wins over usage_descriptions in this package's config. If you have existing permission copy (localised, legally reviewed), put it there and leave usage_descriptions as the fallback for packages that don't.

Picture-in-picture

PiP is applied by the single post_compile hook nativephp:media:configure (which replaced nativephp:media:enable-pip). If you have build scripts referencing the old command, update them — and if you'd previously hand-patched a VideoActivity, the patch is now anchored to com.protendai.media.VideoActivity.

Gotchas and Tips

Upgrading from a pre-1.0 build

Four things will bite you:

  • NATIVEPHP_MEDIA_LIBRARY defaults to off. If your app browsed the device library, it will stop declaring the permissions after upgrade and permission checks will report denied. Set it explicitly.
  • nativephp:media:enable-pip is gone. Anything invoking it in CI or a build script now fails.
  • The exported Android media session is locked down. Other packages get playback commands only, and onAddMediaItems trusts requestMetadata.mediaUri only from your app. If you relied on an external intent/Auto/Assistant flow to queue arbitrary URIs, that's an intentional security fix, not a bug — redesign around setQueue.
  • Closing the Android video player (or dismissing its PiP window) no longer stops the shared player. If you leaned on that side effect to stop music, call your own stop explicitly.

The -1 sentinel is easy to forget

startPosition = -1 means "resume", and it's the only thing distinguishing resume from restart on setQueue/openVideo. A default of 0 is a silent regression to "always start over". Consider a tiny wrapper (resumeQueue(), restartItem()) so the magic number appears exactly once in your codebase.

skipTo now starts playback on Android

It already did on iOS. If you used skipTo purely to position a paused player, audit those call sites — playback now begins.

Don't assume events arrive while the app is backgrounded on Android

The host activity is held weakly and re-adopted on recreation; events are buffered until it returns. That's a robustness win, but it means a music screen that assumes a continuous event stream while backgrounded will see a burst on return. Make event handlers idempotent and reconcile from permissionStatus()/queue state rather than from deltas.

Native logs are now PII-free

Titles, file paths and URIs have been stripped from native logs. Debugging "wrong track" or "file not found" issues now has to come from your own Log:: calls in PHP/JS, not from logcat/Xcode output. Add structured logging around setQueue before you need it.

probe() is stricter on iOS

Missing files now report ok: false instead of a false-positive ok: true. If you wrote a validation path that trusted probe() as a no-op check, it may start correctly rejecting items it previously passed — which is the point, but expect test churn.

iOS minimum is 16.0

Dropping below 16 in your Podfile or Xcode project settings will fail the build. Worth a one-line note in your upgrade PR.

stop() on iOS releases the audio session

Other apps' audio now resumes after stop(). If you were relying on the session staying warm (e.g. stop then immediately start again with no ducking artefact), test that transition.

Config caching

config/nativephp-media.php is read at build time for the native declarations, so php artisan config:cache in CI is fine — but changing library requires a recompile, not just a config clear. Set it before the native build, not after.

One video player at a time

A second openVideo() while one is in PiP no longer stacks another player. Treat "open a video" as an idempotent operation on a single player instance and close explicitly rather than assuming a new instance replaces the old one visually.

Extension points worth knowing

  • usage_descriptions in config for default permission copy (overridable per-app via nativephp.permissions).
  • The single post_compile hook — if you need additional native patching, add your own hook rather than editing nativephp:media:configure.
  • The JS package's event constants and TypeScript types — use them instead of literals; this is the seam most likely to shift in future releases.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
illuminate/framework
mrclay/shibalike
consoletvs/links
isapp/laravel-forge-mcp
roundly-consulting/safe-fetch-for-laravel
uthman/hashid
magentron/laravel-html
encoredjw/laravel-admin
roundly-consulting/imports-for-laravel
stillat/blade-directives
stonecoldtea/laravel-permission
flareia/laravel-sdk
trotexnet/laravel-h5p
luzucheng59/voyager
djstarcom/laravel-breadcrumbs
djstarcom/laravel-server-timing
class-atlas/laravel-flitt
ipregistry/ipregistry-laravel
daimakuai-ext/config
tomassalazarlopez/laravel-filemanager