MediaPlayer

简介: MediaPlayer

frameworks\base\media\java\android\media\MediaPlayer.java

首先明确一点:MediaPlayer只是向java层暴露了具体播放的接口,其具体实现都是通过JNI接口调用c++代码实现的;

先看一个简单的接口 :start(); 他调用了native层的 _start() 方法

    public void start() throws IllegalStateException {
        //FIXME use lambda to pass startImpl to superclass
        final int delay = getStartDelayMs();
        if (delay == 0) {
            startImpl();
        } else {
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    baseSetStartDelayMs(0);
                    try {
                        startImpl();
                    } catch (IllegalStateException e) {
                        // fail silently for a state exception when it is happening after
                        // a delayed start, as the player state could have changed between the
                        // call to start() and the execution of startImpl()
                    }
                }
            }.start();
        }
    }
private void startImpl() {
        baseStart(0); // unknown device at this point
        stayAwake(true);
        tryToEnableNativeRoutingCallback();
        _start();
    }
 
private native void _start() throws IllegalStateException;

JNI也是用Native语言编写的,它属于Native层。但是为了便于学习和分析,我们将与JNI相关的代码划 分到JNI层,JNI层是Native层的一部分。

JNI是 Java Native Interface的缩写,通过JNI可以让Java方法与Native方法相互调用,其中Native方法一般是用 C/C++编写的。因此,JNI就是连接Java层和Native层的桥梁。要使用JNI需要先加载JNI库,在 MediaPlayer.java中有如下代码

static {
        System.loadLibrary("media_jni");
        native_init();
    }

在静态代码块中加载JNI库,并调用native_init方法。这里加载的名为"media_jni"的 JIN 库指的是 libmedia.so。

MediaPlayer 的 JNI 层的代码在 frameworks/base/media/jni/android_media_MediaPlayer.cpp中。我们需要通过JNI调用Native方法,那么就应该有一个结构来保存Java层Native方法与JNI层方法的对应关系,这个结构就是 android_media_MediaPlayer.cpp中定义的JNINativeMethod数组,代码如下所示

static const JNINativeMethod gMethods[] = {
    {
        "nativeSetDataSource",
        "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
        "[Ljava/lang/String;)V",
        (void *)android_media_MediaPlayer_setDataSourceAndHeaders
    },
 
    {"_setDataSource",      "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},
    {"_setDataSource",      "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback },
    {"_setVideoSurface",    "(Landroid/view/Surface;)V",        (void *)android_media_MediaPlayer_setVideoSurface},
    {"_prepare",            "()V",                              (void *)android_media_MediaPlayer_prepare},
    {"prepareAsync",        "()V",                              (void *)android_media_MediaPlayer_prepareAsync},
    {"_start",              "()V",                              (void *)android_media_MediaPlayer_start},
    {"_stop",               "()V",                              (void *)android_media_MediaPlayer_stop},
    {"getVideoWidth",       "()I",                              (void *)android_media_MediaPlayer_getVideoWidth},
    {"getVideoHeight",      "()I",                              (void *)android_media_MediaPlayer_getVideoHeight},
    {"native_getMetrics",   "()Landroid/os/PersistableBundle;", (void *)android_media_MediaPlayer_native_getMetrics},
    {"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer_setPlaybackParams},
    {"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer_getPlaybackParams},
    {"setSyncParams",     "(Landroid/media/SyncParams;)V",  (void *)android_media_MediaPlayer_setSyncParams},
    {"getSyncParams",     "()Landroid/media/SyncParams;",   (void *)android_media_MediaPlayer_getSyncParams},
    {"_seekTo",             "(JI)V",                            (void *)android_media_MediaPlayer_seekTo},
    {"_notifyAt",           "(J)V",                             (void *)android_media_MediaPlayer_notifyAt},
    {"_pause",              "()V",                              (void *)android_media_MediaPlayer_pause},
    {"isPlaying",           "()Z",                              (void *)android_media_MediaPlayer_isPlaying},
    {"getCurrentPosition",  "()I",                              (void *)android_media_MediaPlayer_getCurrentPosition},
    {"getDuration",         "()I",                              (void *)android_media_MediaPlayer_getDuration},
    {"_release",            "()V",                              (void *)android_media_MediaPlayer_release},
    {"_reset",              "()V",                              (void *)android_media_MediaPlayer_reset},
    {"_setAudioStreamType", "(I)V",                             (void *)android_media_MediaPlayer_setAudioStreamType},
    {"_getAudioStreamType", "()I",                              (void *)android_media_MediaPlayer_getAudioStreamType},
    {"setParameter",        "(ILandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_setParameter},
    {"setLooping",          "(Z)V",                             (void *)android_media_MediaPlayer_setLooping},
    {"isLooping",           "()Z",                              (void *)android_media_MediaPlayer_isLooping},
    {"_setVolume",          "(FF)V",                            (void *)android_media_MediaPlayer_setVolume},
    {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},
    {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
    {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
    {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
    {"native_setup",        "(Ljava/lang/Object;Landroid/os/Parcel;)V",(void *)android_media_MediaPlayer_native_setup},
    {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
    {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
    {"native_setAudioSessionId",   "(I)V",                      (void *)android_media_MediaPlayer_set_audio_session_id},
    {"_setAuxEffectSendLevel", "(F)V",                          (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
    {"attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer_attachAuxEffect},
    {"native_pullBatteryData", "(Landroid/os/Parcel;)I",        (void *)android_media_MediaPlayer_pullBatteryData},
    {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I",  (void *)android_media_MediaPlayer_setRetransmitEndpoint},
    {"setNextMediaPlayer",  "(Landroid/media/MediaPlayer;)V",   (void *)android_media_MediaPlayer_setNextMediaPlayer},
    {"native_applyVolumeShaper",
                            "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I",
                                                                (void *)android_media_MediaPlayer_applyVolumeShaper},
    {"native_getVolumeShaperState",
                            "(I)Landroid/media/VolumeShaper$State;",
                                                                (void *)android_media_MediaPlayer_getVolumeShaperState},
    // Modular DRM
    { "_prepareDrm", "([B[B)V",                                 (void *)android_media_MediaPlayer_prepareDrm },
    { "_releaseDrm", "()V",                                     (void *)android_media_MediaPlayer_releaseDrm },
 
    // AudioRouting
    {"native_setOutputDevice", "(I)Z",                          (void *)android_media_MediaPlayer_setOutputDevice},
    {"native_getRoutedDeviceId", "()I",                         (void *)android_media_MediaPlayer_getRoutedDeviceId},
    {"native_enableDeviceCallback", "(Z)V",                     (void *)android_media_MediaPlayer_enableDeviceCallback},
};

从上面来看,JNINativeMethod数组有3个参数。

• 第一个参数,比如"_prepare",它是Java 层Native 方法的名称。在Java层调用Native方法,交由JNI层来 实现。

• 第二个参数是 Java 层 Native 方法的参数和返回值。其中()中的字符代表参数,后面的字母则代表 返回值。

• 第三个参数是Java层Native方法对应的JNI层的方法,比如_prepare方法对应JNI层的方法为 android_media_MediaPlayer_prepare。通过JNI层的方法就可以调用Native层相应的方法

前面在MediaPlayer.java的静态代码块中调用了native_init方法,这个方法是一个Native方法,查询 JNINativeMethod数组,它对应的是android_media_MediaPlayer_native_init方法,如下所示:

// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in MediaPlayer, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
    jclass clazz;
    //JNI层调用Java层,获取MediaPlayer对象
    clazz = env->FindClass("android/media/MediaPlayer");
    if (clazz == NULL) {
        return;
    }
 
    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }
    //获取Java层的postEventFromNative方法
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }
 
    fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
    if (fields.surface_texture == NULL) {
        return;
    }
 
    env->DeleteLocalRef(clazz);
 
    clazz = env->FindClass("android/net/ProxyInfo");
    if (clazz == NULL) {
        return;
    }
 
    fields.proxyConfigGetHost =
        env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");
 
    fields.proxyConfigGetPort =
        env->GetMethodID(clazz, "getPort", "()I");
 
    fields.proxyConfigGetExclusionList =
        env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
 
    env->DeleteLocalRef(clazz);
 
    // Modular DRM
    FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
    if (clazz) {
        GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V");
        gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
 
        env->DeleteLocalRef(clazz);
    } else {
        ALOGE("JNI android_media_MediaPlayer_native_init couldn't "
              "get clazz android/media/MediaDrm$MediaDrmStateException");
    }
 
    gPlaybackParamsFields.init(env);
    gSyncParamsFields.init(env);
    gVolumeShaperFields.init(env);
}

Native层可以通过JNI层来调用Java层代码。

static void android_media_MediaPlayer_start(JNIEnv *env, jobject thiz)
{
    ALOGV("start");
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);//得到MediaPlayer的强指针
    if (mp == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    //通过 JNI 层的android_media_MediaPlayer_start方法调用了 Native 层的 start方法
    process_media_player_call( env, thiz, mp->start(), NULL, NULL );
}

 

MediaPlayer的Native层整体是一个C/S(Client/Server)架构,Client端和Server端运行在两个进程中,它们之间通过Binder机制来进行通信。Client端MediaPlayer对应的动态库是libmedia.so,Server端 MediaPlayerService对应的动态库为libmediaservice.so

1.Client端分析

MediaPlayer Client 端的功能实现定义的头文件为 mediaplayer.h,相应的源文件为

frameworks\av\media\libmedia\mediaplayer.cpp

我们 接着来查看mediaplayer.cpp的prepare方法,如下所示 调用了prepareAsync_l方法

// TODO: In case of error, prepareAsync provides the caller with 2 error codes,
// one defined in the Android framework and one provided by the implementation
// that generated the error. The sync version of prepare returns only 1 error
// code.
status_t MediaPlayer::prepare()
{
    ALOGV("prepare");
    Mutex::Autolock _l(mLock);
    mLockThreadId = getThreadId();
    if (mPrepareSync) {
        mLockThreadId = 0;
        return -EALREADY;
    }
    mPrepareSync = true;
    status_t ret = prepareAsync_l();
    if (ret != NO_ERROR) {
        mLockThreadId = 0;
        return ret;
    }
 
    if (mPrepareSync) {
        mSignal.wait(mLock);  // wait for prepare done
        mPrepareSync = false;
    }
    ALOGV("prepare complete - status=%d", mPrepareStatus);
    mLockThreadId = 0;
    return mPrepareStatus;
}
// must call with lock held
status_t MediaPlayer::prepareAsync_l()
{
    if ( (mPlayer != 0) && ( mCurrentState & (MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) {
        if (mAudioAttributesParcel != NULL) {
            mPlayer->setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, *mAudioAttributesParcel);
        } else {
            mPlayer->setAudioStreamType(mStreamType);
        }
        mCurrentState = MEDIA_PLAYER_PREPARING;
        return mPlayer->prepareAsync();
    }
    ALOGE("prepareAsync called in state %d, mPlayer(%p)", mCurrentState, mPlayer.get());
    return INVALID_OPERATION;
}

调用了mPlayer的prepareAsync方法,那么mPlayer指的是什么呢?我们带着这个问题 来查看mediaplayer.cpp的setDataSource方法

status_t MediaPlayer::setDataSource(const sp<IDataSource> &source)
{
    ALOGV("setDataSource(IDataSource)");
    status_t err = UNKNOWN_ERROR;
    //通过getMediaPlayerService方法得到IMediaPlayerService指针
    //IMediaPlayerService指针指向的就是MediaPlayer的Service端:MediaPlayerService
    const sp<IMediaPlayerService> service(getMediaPlayerService());
    if (service != 0) {
    //通过IMediaPlayerService的create方法得到
    //IMediaPlayer指针。通过IMediaPlayer指针就可以调用MediaPlayerService所提供的各种功能
        sp<IMediaPlayer> player(service->create(this, mAudioSessionId, mAttributionSource));
        if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
            (NO_ERROR != player->setDataSource(source))) {
            player.clear();
        }
        err = attachNewPlayer(player);
    }
    return err;
}
status_t MediaPlayer::attachNewPlayer(const sp<IMediaPlayer>& player)
{
    status_t err = UNKNOWN_ERROR;
    sp<IMediaPlayer> p;
    { // scope for the lock
        Mutex::Autolock _l(mLock);
 
        if ( !( (mCurrentState & MEDIA_PLAYER_IDLE) ||
                (mCurrentState == MEDIA_PLAYER_STATE_ERROR ) ) ) {
            ALOGE("attachNewPlayer called in state %d", mCurrentState);
            return INVALID_OPERATION;
        }
 
        clear_l();
        p = mPlayer;
        mPlayer = player;
        if (player != 0) {
            mCurrentState = MEDIA_PLAYER_INITIALIZED;
            err = NO_ERROR;
        } else {
            ALOGE("Unable to create media player");
        }
    }
 
    if (p != 0) {
        p->disconnect();
    }
 
    return err;
}

attachNewPlayer方法将player赋值给mPlayer。因此,前面遗留下的问题得到了解决:mPlayer指的就是 IMediaPlayer指针,调用mPlayer的prepareAsync方法其实就是调用MediaPlayerService的prepareAsync方法。

2.Server端分析

在多媒体架构中除了C/S架构中的Client和Server,还有一个全局的ServiceManager,它用来管理系统中 的各种Service。Client、Server和ServiceManager的关系如图所示。

1.首先Server进程会注册一些Service到ServiceManager中

2.Client要使用某个Service,则需要先到ServiceManager查询Service的相关信息

3.然后Client根据Service的相关信息与Service所在的Server进程建立通信通路,这样Client就可以使用Service了。

Android的多媒体服务是由一个叫作 MediaServer的服务进程提供的,它是一个可执行程序,在Android系统启动时,MediaServer也会被启动。

frameworks\av\media\mediaserver\main_mediaserver.cpp

int main(int argc __unused, char **argv __unused)
{
    signal(SIGPIPE, SIG_IGN);
 
    sp<ProcessState> proc(ProcessState::self());
    //得到IServiceManager指针,这样我们就可以与另一个进程的ServiceManager进行通信。
    sp<IServiceManager> sm(defaultServiceManager());
    ALOGI("ServiceManager: %p", sm.get());
    //注册服务
    MediaPlayerService::instantiate();
    ResourceManagerService::instantiate();
    registerExtensions();
    ::android::hardware::configureRpcThreadpool(16, false);
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    ::android::hardware::joinRpcThreadpool();
}

frameworks\av\media\libmediaplayerservice\MediaPlayerService.cpp

向 ServiceManager 添加一个名为media.player 的 MediaPlayerService 服务。这样 MediaPlayerService 就被添加到 ServiceManager中,MediaPlayer 就可以通过 字符串"media.player"来查询 MediaPlayerService。

void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService(
            String16("media.player"), new MediaPlayerService());
}

MediaPlayer是在setDataSource的getMediaPlayerService方法中查询MediaPlayerService的

frameworks\av\media\libmedia\IMediaDeathNotifier.cpp

// establish binder interface to MediaPlayerService
/*static*/const sp<IMediaPlayerService>
IMediaDeathNotifier::getMediaPlayerService()
{
    ALOGV("getMediaPlayerService");
    Mutex::Autolock _l(sServiceLock);
    if (sMediaPlayerService == 0) {
        //得到 IServiceManager 指针
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
        //查询名称为"media.player"的服务。这样MediaPlayer就获取了MediaPlayerService的信息,
        //通过这些信息就可以与在MediaServer进程中的MediaPlayerService进行通信了
            binder = sm->getService(String16("media.player"));
            if (binder != 0) {
                break;
            }
            ALOGW("Media player service not published, waiting...");
            usleep(500000); // 0.5 s
        } while (true);
 
        if (sDeathNotifier == NULL) {
            sDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(sDeathNotifier);
        sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
    }
    ALOGE_IF(sMediaPlayerService == 0, "no media player service!?");
    return sMediaPlayerService;
}

当我们调用MediaPlayer的setDataSource方法时,会调用MediaPlayerService 的setDataSource方法: frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp

status_t MediaPlayerService::Client::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url,
        const KeyedVector<String8, String8> *headers)
{
    ALOGV("setDataSource(%s)", url);
    if (url == NULL)
        return UNKNOWN_ERROR;
 
    if ((strncmp(url, "http://", 7) == 0) ||
        (strncmp(url, "https://", 8) == 0) ||
        (strncmp(url, "rtsp://", 7) == 0)) {
        if (!checkPermission("android.permission.INTERNET")) {
            return PERMISSION_DENIED;
        }
    }
 
    if (strncmp(url, "content://", 10) == 0) {
        // get a filedescriptor for the content Uri and
        // pass it to the setDataSource(fd) method
 
        String16 url16(url);
        int fd = android::openContentProviderFile(url16);
        if (fd < 0)
        {
            ALOGE("Couldn't open fd for %s", url);
            return UNKNOWN_ERROR;
        }
        status_t status = setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus
        close(fd);
        return mStatus = status;
    } else {
        //获取播放器的类型playerType
        player_type playerType = MediaPlayerFactory::getPlayerType(this, url);
        sp<MediaPlayerBase> p = setDataSource_pre(playerType);
        if (p == NULL) {
            return NO_INIT;
        }
 
        return mStatus =
                setDataSource_post(
                p, p->setDataSource(httpService, url, headers));
    }
}
sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(
        player_type playerType)
{
    ALOGV("player type = %d", playerType);
 
    // create the right type of player
    sp<MediaPlayerBase> p = createPlayer(playerType);
}
sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)
{
    // determine if we have the right player type
    sp<MediaPlayerBase> p = getPlayer();
    if ((p != NULL) && (p->playerType() != playerType)) {
        ALOGV("delete player");
        p.clear();
    }
    if (p == NULL) {
        p = MediaPlayerFactory::createPlayer(playerType, mListener,
            VALUE_OR_FATAL(aidl2legacy_int32_t_pid_t(mAttributionSource.pid)));
    }
 
    if (p != NULL) {
        p->setUID(VALUE_OR_FATAL(aidl2legacy_int32_t_uid_t(mAttributionSource.uid)));
    }
 
    return p;
}

frameworks\av\media\libmediaplayerservice\MediaPlayerFactory.cpp

sp<MediaPlayerBase> MediaPlayerFactory::createPlayer(
        player_type playerType,
        const sp<MediaPlayerBase::Listener> &listener,
        pid_t pid) {
    sp<MediaPlayerBase> p;
    IFactory* factory;
    status_t init_result;
    Mutex::Autolock lock_(&sLock);
 
    if (sFactoryMap.indexOfKey(playerType) < 0) {
        ALOGE("Failed to create player object of type %d, no registered"
              " factory", playerType);
        return p;
    }
 
    factory = sFactoryMap.valueFor(playerType);
    CHECK(NULL != factory);
    p = factory->createPlayer(pid);
 
    if (p == NULL) {
        ALOGE("Failed to create player object of type %d, create failed",
               playerType);
        return p;
    }
 
    init_result = p->initCheck();
    if (init_result == NO_ERROR) {
        p->setNotifyCallback(listener);
    } else {
        ALOGE("Failed to create player object of type %d, initCheck failed"
              " (res = %d)", playerType, init_result);
        p.clear();
    }
 
    return p;
}

在Android 6.0中, MediaPlayerFactory 中有 3 个播放器 Factory,分别是 StagefrightPlayerFactory、NuPlayerFactory和 TestPlayerFactory。其中,StagefrightPlayerFactory用于创建Stagefright框架。Stagefright 框架在 Android 2.0 中被引入系统以替代庞大复杂的 OpenCore 框架。但是由于Stagefright 存在严重漏洞,并被黑客所利用,因 此在 Android 7.0 以后中,谷歌去掉了StagefrightPlayerFactory,也就是Stagefright框架。默认的播放器使用谷歌自己研发的NuPlayer框架。NuPlayerFactory的createPlayer方法如下所示

virtual sp<MediaPlayerBase> createPlayer(pid_t pid) {
        ALOGV(" create NuPlayer");
        return new NuPlayerDriver(pid);
    }

目录
相关文章
|
3月前
|
Android开发
Android 利用MediaPlayer实现音乐播放
本文提供了一个简单的Android MediaPlayer音乐播放示例,包括创建PlayerActivity、配置AndroidManifest.xml和activity_player.xml布局,以及实现播放和暂停功能的代码。
26 0
Android 利用MediaPlayer实现音乐播放
|
6月前
|
Java Android开发 C++
MediaPlayer
MediaPlayer
77 2
|
5月前
|
API Android开发 UED
56. 【Android教程】媒体播放器:MediaPlayer
56. 【Android教程】媒体播放器:MediaPlayer
100 0
|
Android开发
Android 使用MediaPlayer和SurfaceView播放视频
Android 使用MediaPlayer和SurfaceView播放视频
166 0
Android-MediaPlayer(3)加打碟旋转效果
Android-MediaPlayer(3)加打碟旋转效果
210 0
Android-MediaPlayer(3)加打碟旋转效果
|
XML Android开发 数据格式
Android MediaPlayer音频播放器详解
Android MediaPlayer音频播放器详解
581 0
Android MediaPlayer音频播放器详解
|
Android开发
Android TextureView &amp; MediaPlayer implements video media player
Android TextureView & MediaPlayer implements video media player code: import android.
1169 0