Bug 1257777 - Part 4: Implement remote codec proxy. r=snorp draft
authorJohn Lin <jolin@mozilla.com>
Fri, 05 Aug 2016 15:23:47 +0800
changeset 401594 a2f66657f372f249c0da3acb292d45e5b5572b18
parent 401593 3fd362ab3220de2a6046a1a9bccaf8f7b1d1280f
child 401595 1df07b94b293a7067044558c3ee42dd8a90bb8da
push id26504
push userbmo:jolin@mozilla.com
push dateWed, 17 Aug 2016 06:44:30 +0000
reviewerssnorp
bugs1257777
milestone51.0a1
Bug 1257777 - Part 4: Implement remote codec proxy. r=snorp MozReview-Commit-ID: 3YEfXXkg2Xi
mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
mobile/android/base/moz.build
widget/android/GeneratedJNINatives.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
@@ -0,0 +1,350 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.media;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.mozglue.JNIObject;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaFormat;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.LinkedList;
+import java.util.List;
+
+// Proxy class of ICodec binder.
+public final class CodecProxy {
+    private static final String LOGTAG = "GeckoRemoteCodecProxy";
+    private static final boolean DEBUG = false;
+
+    private static final RemoteManager sRemoteManager = new RemoteManager();
+
+    private ICodec mRemote;
+    private FormatParam mFormat;
+    private Surface mOutputSurface;
+    private CallbacksForwarder mCallbacks;
+
+    public interface Callbacks {
+        void onInputExhausted();
+        void onOutputFormatChanged(MediaFormat format);
+        void onOutput(byte[] bytes, BufferInfo info);
+        void onError(boolean fatal);
+    }
+
+    @WrapForJNI
+    public static class NativeCallbacks extends JNIObject implements Callbacks {
+        public native void onInputExhausted();
+        public native void onOutputFormatChanged(MediaFormat format);
+        public native void onOutput(byte[] bytes, BufferInfo info);
+        public native void onError(boolean fatal);
+
+        @Override // JNIObject
+        protected native void disposeNative();
+    }
+
+    private static class CallbacksForwarder extends ICodecCallbacks.Stub {
+        private final Callbacks mCallbacks;
+        // // Store the latest frame in case we receive dummy sample.
+        private byte[] mPrevBytes;
+
+        CallbacksForwarder(Callbacks callbacks) {
+            mCallbacks = callbacks;
+        }
+
+        @Override
+        public void onInputExhausted() throws RemoteException {
+            mCallbacks.onInputExhausted();
+        }
+
+        @Override
+        public void onOutputFormatChanged(FormatParam format) throws RemoteException {
+            mCallbacks.onOutputFormatChanged(format.asFormat());
+        }
+
+        @Override
+        public void onOutput(Sample sample) throws RemoteException {
+            byte[] bytes = null;
+            if (sample.isDummy()) { // Dummy sample.
+                bytes = mPrevBytes;
+            } else {
+                mPrevBytes = bytes = sample.getBytes();
+            }
+            mCallbacks.onOutput(bytes, sample.info);
+        }
+
+        @Override
+        public void onError(boolean fatal) throws RemoteException {
+            reportError(fatal);
+        }
+
+        public void reportError(boolean fatal) {
+            mCallbacks.onError(fatal);
+        }
+    }
+
+    private static final class RemoteManager implements IBinder.DeathRecipient {
+        private List<CodecProxy> mProxies = new LinkedList<CodecProxy>();
+        private ICodecManager mRemote;
+        private volatile CountDownLatch mConnectionLatch;
+        private final ServiceConnection mConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                if (DEBUG) Log.d(LOGTAG, "service connected");
+                try {
+                    service.linkToDeath(RemoteManager.this, 0);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                mRemote = ICodecManager.Stub.asInterface(service);
+                if (mConnectionLatch != null) {
+                    mConnectionLatch.countDown();
+                }
+            }
+
+            /**
+             * Called when a connection to the Service has been lost.  This typically
+             * happens when the process hosting the service has crashed or been killed.
+             * This does <em>not</em> remove the ServiceConnection itself -- this
+             * binding to the service will remain active, and you will receive a call
+             * to {@link #onServiceConnected} when the Service is next running.
+             *
+             * @param name The concrete component name of the service whose
+             *             connection has been lost.
+             */
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                if (DEBUG) Log.d(LOGTAG, "service disconnected");
+                mRemote.asBinder().unlinkToDeath(RemoteManager.this, 0);
+                mRemote = null;
+                if (mConnectionLatch != null) {
+                    mConnectionLatch.countDown();
+                }
+            }
+        };
+
+        public synchronized boolean init() {
+            if (mRemote != null) {
+                return true;
+            }
+
+            if (DEBUG) Log.d(LOGTAG, "init remote manager " + this);
+            Context appCtxt = GeckoAppShell.getApplicationContext();
+            if (DEBUG) Log.d(LOGTAG, "ctxt=" + appCtxt);
+            appCtxt.bindService(new Intent(appCtxt, CodecManager.class),
+                    mConnection, Context.BIND_AUTO_CREATE);
+            if (!waitConnection()) {
+                appCtxt.unbindService(mConnection);
+                return false;
+            }
+            return true;
+        }
+
+        private boolean waitConnection() {
+            boolean ok = false;
+
+            mConnectionLatch = new CountDownLatch(1);
+            try {
+                int retryCount = 0;
+                while (retryCount < 5) {
+                    if (DEBUG) Log.d(LOGTAG, "waiting for connection latch:" + mConnectionLatch);
+                    mConnectionLatch.await(1, TimeUnit.SECONDS);
+                    if (mConnectionLatch.getCount() == 0) {
+                        break;
+                    }
+                    Log.w(LOGTAG, "Creator not connected in 1s. Try again.");
+                    retryCount++;
+                }
+                ok = true;
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG, "service not connected in 5 seconds. Stop waiting.");
+                e.printStackTrace();
+            }
+            mConnectionLatch = null;
+
+            return ok;
+        }
+
+        public synchronized CodecProxy createCodec(MediaFormat format, Surface surface, Callbacks callbacks) {
+            try {
+                ICodec remote = mRemote.createCodec();
+                CodecProxy proxy = new CodecProxy(format, surface, callbacks);
+                if (proxy.init(remote)) {
+                    mProxies.add(proxy);
+                    return proxy;
+                } else {
+                    return null;
+                }
+            } catch (RemoteException e) {
+                e.printStackTrace();
+                return null;
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            Log.e(LOGTAG, "remote codec is dead");
+            handleRemoteDeath();
+        }
+
+        private synchronized void handleRemoteDeath() {
+            // Wait for onServiceDisconnected()
+            if (!waitConnection()) {
+                notifyError(true);
+                return;
+            }
+            // Restart
+            if (init() && recoverRemoteCodec()) {
+                notifyError(false);
+            } else {
+                notifyError(true);
+            }
+        }
+
+        private synchronized void notifyError(boolean fatal) {
+            for (CodecProxy proxy : mProxies) {
+                proxy.mCallbacks.reportError(fatal);
+            }
+        }
+
+        private synchronized boolean recoverRemoteCodec() {
+            if (DEBUG) Log.d(LOGTAG, "recover codec");
+            boolean ok = true;
+            try {
+                for (CodecProxy proxy : mProxies) {
+                    ok &= proxy.init(mRemote.createCodec());
+                }
+                return ok;
+            } catch (RemoteException e) {
+                return false;
+            }
+        }
+
+        private void releaseCodec(CodecProxy proxy) throws DeadObjectException, RemoteException {
+            proxy.deinit();
+            synchronized (this) {
+                if (mProxies.remove(proxy) && mProxies.isEmpty()) {
+                    release();
+                }
+            }
+        }
+
+        private void release() {
+            if (DEBUG) Log.d(LOGTAG, "release remote manager " + this);
+            Context appCtxt = GeckoAppShell.getApplicationContext();
+            mRemote.asBinder().unlinkToDeath(this, 0);
+            mRemote = null;
+            appCtxt.unbindService(mConnection);
+        }
+    }
+
+    @WrapForJNI
+    public static CodecProxy create(MediaFormat format, Surface surface, Callbacks callbacks) {
+        if (!sRemoteManager.init()) {
+            return null;
+        }
+        return sRemoteManager.createCodec(format, surface, callbacks);
+    }
+
+    private CodecProxy(MediaFormat format, Surface surface, Callbacks callbacks) {
+        mFormat = new FormatParam(format);
+        mOutputSurface = surface;
+        mCallbacks = new CallbacksForwarder(callbacks);
+    }
+
+    private boolean init(ICodec remote) {
+        try {
+            remote.setCallbacks(mCallbacks);
+            remote.configure(mFormat, mOutputSurface, 0);
+            remote.start();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return false;
+        }
+
+        mRemote = remote;
+        return true;
+    }
+
+    private boolean deinit() {
+        try {
+            mRemote.stop();
+            mRemote.release();
+            mRemote = null;
+            return true;
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    @WrapForJNI
+    public synchronized boolean input(byte[] bytes, BufferInfo info) {
+        if (mRemote == null) {
+            Log.e(LOGTAG, "cannot send input to an ended codec");
+            return false;
+        }
+        Sample sample = (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) ?
+                        Sample.EOS : new Sample(ByteBuffer.wrap(bytes), info);
+        try {
+            mRemote.queueInput(sample);
+        } catch (DeadObjectException e) {
+            return false;
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            Log.e(LOGTAG, "fail to input sample:" + sample);
+            return false;
+        }
+        return true;
+    }
+
+    @WrapForJNI
+    public synchronized boolean flush() {
+        if (mRemote == null) {
+            Log.e(LOGTAG, "cannot flush an ended codec");
+            return false;
+        }
+        try {
+            if (DEBUG) Log.d(LOGTAG, "flush " + this);
+            mRemote.flush();
+        } catch (DeadObjectException e) {
+            return false;
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    @WrapForJNI
+    public synchronized boolean release() {
+        if (mRemote == null) {
+            Log.w(LOGTAG, "codec already ended");
+            return true;
+        }
+        if (DEBUG) Log.d(LOGTAG, "release " + this);
+        try {
+            sRemoteManager.releaseCodec(this);
+        } catch (DeadObjectException e) {
+            return false;
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -504,16 +504,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'lwt/LightweightTheme.java',
     'lwt/LightweightThemeDrawable.java',
     'mdns/MulticastDNSManager.java',
     'media/AsyncCodec.java',
     'media/AsyncCodecFactory.java',
     'media/AudioFocusAgent.java',
     'media/Codec.java',
     'media/CodecManager.java',
+    'media/CodecProxy.java',
     'media/FormatParam.java',
     'media/JellyBeanAsyncCodec.java',
     'media/MediaControlService.java',
     'media/Sample.java',
     'MediaCastingBar.java',
     'MemoryMonitor.java',
     'menu/GeckoMenu.java',
     'menu/GeckoMenuInflater.java',
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -496,16 +496,47 @@ const JNINativeMethod NativePanZoomContr
             ::template Wrap<&Impl::AbortAnimation>),
 
     mozilla::jni::MakeNativeMethod<NativePanZoomController::SetIsLongpressEnabled_t>(
             mozilla::jni::NativeStub<NativePanZoomController::SetIsLongpressEnabled_t, Impl>
             ::template Wrap<&Impl::SetIsLongpressEnabled>)
 };
 
 template<class Impl>
+class CodecProxy::NativeCallbacks::Natives : public mozilla::jni::NativeImpl<NativeCallbacks, Impl>
+{
+public:
+    static const JNINativeMethod methods[5];
+};
+
+template<class Impl>
+const JNINativeMethod CodecProxy::NativeCallbacks::Natives<Impl>::methods[] = {
+
+    mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::DisposeNative_t>(
+            mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::DisposeNative_t, Impl>
+            ::template Wrap<&Impl::DisposeNative>),
+
+    mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnError_t>(
+            mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnError_t, Impl>
+            ::template Wrap<&Impl::OnError>),
+
+    mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnInputExhausted_t>(
+            mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnInputExhausted_t, Impl>
+            ::template Wrap<&Impl::OnInputExhausted>),
+
+    mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnOutput_t>(
+            mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnOutput_t, Impl>
+            ::template Wrap<&Impl::OnOutput>),
+
+    mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnOutputFormatChanged_t>(
+            mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnOutputFormatChanged_t, Impl>
+            ::template Wrap<&Impl::OnOutputFormatChanged>)
+};
+
+template<class Impl>
 class NativeJSContainer::Natives : public mozilla::jni::NativeImpl<NativeJSContainer, Impl>
 {
 public:
     static const JNINativeMethod methods[2];
 };
 
 template<class Impl>
 const JNINativeMethod NativeJSContainer::Natives<Impl>::methods[] = {
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -2016,16 +2016,77 @@ auto AudioFocusAgent::NotifyStartedPlayi
 constexpr char AudioFocusAgent::NotifyStoppedPlaying_t::name[];
 constexpr char AudioFocusAgent::NotifyStoppedPlaying_t::signature[];
 
 auto AudioFocusAgent::NotifyStoppedPlaying() -> void
 {
     return mozilla::jni::Method<NotifyStoppedPlaying_t>::Call(AudioFocusAgent::Context(), nullptr);
 }
 
+const char CodecProxy::name[] =
+        "org/mozilla/gecko/media/CodecProxy";
+
+constexpr char CodecProxy::Create_t::name[];
+constexpr char CodecProxy::Create_t::signature[];
+
+auto CodecProxy::Create(mozilla::jni::Object::Param a0, mozilla::jni::Object::Param a1, mozilla::jni::Object::Param a2) -> CodecProxy::LocalRef
+{
+    return mozilla::jni::Method<Create_t>::Call(CodecProxy::Context(), nullptr, a0, a1, a2);
+}
+
+constexpr char CodecProxy::Flush_t::name[];
+constexpr char CodecProxy::Flush_t::signature[];
+
+auto CodecProxy::Flush() const -> bool
+{
+    return mozilla::jni::Method<Flush_t>::Call(CodecProxy::mCtx, nullptr);
+}
+
+constexpr char CodecProxy::Input_t::name[];
+constexpr char CodecProxy::Input_t::signature[];
+
+auto CodecProxy::Input(mozilla::jni::ByteArray::Param a0, mozilla::jni::Object::Param a1) const -> bool
+{
+    return mozilla::jni::Method<Input_t>::Call(CodecProxy::mCtx, nullptr, a0, a1);
+}
+
+constexpr char CodecProxy::Release_t::name[];
+constexpr char CodecProxy::Release_t::signature[];
+
+auto CodecProxy::Release() const -> bool
+{
+    return mozilla::jni::Method<Release_t>::Call(CodecProxy::mCtx, nullptr);
+}
+
+const char CodecProxy::NativeCallbacks::name[] =
+        "org/mozilla/gecko/media/CodecProxy$NativeCallbacks";
+
+constexpr char CodecProxy::NativeCallbacks::New_t::name[];
+constexpr char CodecProxy::NativeCallbacks::New_t::signature[];
+
+auto CodecProxy::NativeCallbacks::New() -> NativeCallbacks::LocalRef
+{
+    return mozilla::jni::Constructor<New_t>::Call(NativeCallbacks::Context(), nullptr);
+}
+
+constexpr char CodecProxy::NativeCallbacks::DisposeNative_t::name[];
+constexpr char CodecProxy::NativeCallbacks::DisposeNative_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnError_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnError_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnInputExhausted_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnInputExhausted_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnOutput_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnOutput_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::signature[];
+
 const char Restrictions::name[] =
         "org/mozilla/gecko/restrictions/Restrictions";
 
 constexpr char Restrictions::IsAllowed_t::name[];
 constexpr char Restrictions::IsAllowed_t::signature[];
 
 auto Restrictions::IsAllowed(int32_t a0, mozilla::jni::String::Param a1) -> bool
 {
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -5878,16 +5878,232 @@ public:
 
     static auto NotifyStoppedPlaying() -> void;
 
     static const mozilla::jni::CallingThread callingThread =
             mozilla::jni::CallingThread::GECKO;
 
 };
 
+class CodecProxy : public mozilla::jni::ObjectBase<CodecProxy>
+{
+public:
+    static const char name[];
+
+    explicit CodecProxy(const Context& ctx) : ObjectBase<CodecProxy>(ctx) {}
+
+    class NativeCallbacks;
+
+    struct Create_t {
+        typedef CodecProxy Owner;
+        typedef CodecProxy::LocalRef ReturnType;
+        typedef CodecProxy::Param SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param,
+                mozilla::jni::Object::Param,
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "create";
+        static constexpr char signature[] =
+                "(Landroid/media/MediaFormat;Landroid/view/Surface;Lorg/mozilla/gecko/media/CodecProxy$Callbacks;)Lorg/mozilla/gecko/media/CodecProxy;";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto Create(mozilla::jni::Object::Param, mozilla::jni::Object::Param, mozilla::jni::Object::Param) -> CodecProxy::LocalRef;
+
+    struct Flush_t {
+        typedef CodecProxy Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "flush";
+        static constexpr char signature[] =
+                "()Z";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto Flush() const -> bool;
+
+    struct Input_t {
+        typedef CodecProxy Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::ByteArray::Param,
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "input";
+        static constexpr char signature[] =
+                "([BLandroid/media/MediaCodec$BufferInfo;)Z";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto Input(mozilla::jni::ByteArray::Param, mozilla::jni::Object::Param) const -> bool;
+
+    struct Release_t {
+        typedef CodecProxy Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "release";
+        static constexpr char signature[] =
+                "()Z";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto Release() const -> bool;
+
+    static const mozilla::jni::CallingThread callingThread =
+            mozilla::jni::CallingThread::ANY;
+
+};
+
+class CodecProxy::NativeCallbacks : public mozilla::jni::ObjectBase<NativeCallbacks>
+{
+public:
+    static const char name[];
+
+    explicit NativeCallbacks(const Context& ctx) : ObjectBase<NativeCallbacks>(ctx) {}
+
+    struct New_t {
+        typedef NativeCallbacks Owner;
+        typedef NativeCallbacks::LocalRef ReturnType;
+        typedef NativeCallbacks::Param SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "<init>";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto New() -> NativeCallbacks::LocalRef;
+
+    struct DisposeNative_t {
+        typedef NativeCallbacks Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "disposeNative";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    struct OnError_t {
+        typedef NativeCallbacks Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                bool> Args;
+        static constexpr char name[] = "onError";
+        static constexpr char signature[] =
+                "(Z)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    struct OnInputExhausted_t {
+        typedef NativeCallbacks Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "onInputExhausted";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    struct OnOutput_t {
+        typedef NativeCallbacks Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::ByteArray::Param,
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "onOutput";
+        static constexpr char signature[] =
+                "([BLandroid/media/MediaCodec$BufferInfo;)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    struct OnOutputFormatChanged_t {
+        typedef NativeCallbacks Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "onOutputFormatChanged";
+        static constexpr char signature[] =
+                "(Landroid/media/MediaFormat;)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static const mozilla::jni::CallingThread callingThread =
+            mozilla::jni::CallingThread::ANY;
+
+    template<class Impl> class Natives;
+};
+
 class Restrictions : public mozilla::jni::ObjectBase<Restrictions>
 {
 public:
     static const char name[];
 
     explicit Restrictions(const Context& ctx) : ObjectBase<Restrictions>(ctx) {}
 
     struct IsAllowed_t {