Android 自定义视频播放器

Android实战——简单网络视频播放器,android实战 String url = "http://baobab.kaiyanapp.com/api/v4/tabs/selected?udid=11111&vc=168&vn=3.3.1&deviceModel=Huawei6&first_channel=eyepetizer_baidu_market&last_channel=eyepetizer_baidu_market&system_version_code=20 可以通过bejson.com这个网站来格式化Js 阅读详情

由于录像之后,原先选用的腾讯VOD点播播放器显示出来竖屏都变横屏了,虽然选中了现在的腾讯VOD点播,还是把Android视频播放器了解了一番。

Android自定义视频播放器有以下三种:

一、MediaPlayer与SurfaceView相结合

// 为SurfaceHolder添加回调
mSurfaceView.getHolder().addCallback(callback);
// 4.0版本之下需要设置的属性
// 设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面
mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// SurfaceHolder回调
private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
    // SurfaceHolder被修改的时候回调
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i(TAG, "SurfaceHolder 被销毁");
        // 销毁SurfaceHolder的时候记录当前的播放位置并停止播放
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            currentPosition = mediaPlayer.getCurrentPosition();
            mediaPlayer.stop();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.i(TAG, "SurfaceHolder 被创建");
        if (currentPosition > 0) {
            // 创建SurfaceHolder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放
            play(currentPosition);
            currentPosition = 0;
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
                               int height) {
        Log.i(TAG, "SurfaceHolder 大小被改变");
    }

};
// 播放本地视频
private void showLocalVideo(final int msec) {
    Log.i(TAG, " 获取视频文件地址");
    String path = mPath.getText().toString().trim();
    File file = new File(path);
    if (!file.exists()) {
        Toast.makeText(this, "视频文件路径错误", Toast.LENGTH_SHORT).show();
        return;
    }
    Log.i(TAG, "指定视频源路径");
    try {
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        // 设置播放的视频源
        mediaPlayer.setDataSource(file.getAbsolutePath());
        // 设置显示视频的SurfaceHolder
        mediaPlayer.setDisplay(mSurfaceView.getHolder());
        Log.i(TAG, "开始装载");
        mediaPlayer.prepareAsync();
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

            @Override
            public void onPrepared(MediaPlayer mp) {
                Log.i(TAG, "装载完成");
                mediaPlayer.start();
                // 按照初始位置播放
                mediaPlayer.seekTo(msec);
                // 设置进度条的最大进度为视频流的最大播放时长
                mSeekBar.setMax(mediaPlayer.getDuration());
                // 开始线程,更新进度条的刻度
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            isPlaying = true;
                            while (isPlaying) {
                                // 如果正在播放,没0.5.毫秒更新一次进度条
                                int current = mediaPlayer.getCurrentPosition();
                                mSeekBar.setProgress(current);

                                sleep(500);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }.start();

                mBtnPlay.setEnabled(false);
            }
        });
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mp) {
                // 在播放完毕被回调
                mBtnPlay.setEnabled(true);
            }
        });

        mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                // 发生错误重新播放
                play(0);
                isPlaying = false;
                return false;
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}
// 播放远程视频
private void showRemoteVideo(final int msec) {
    String videoUrl2 = "http://edu.ismartwork.cn/v/lcl/ftpFile/data/workerCircle/cf7cc922de584d0f88833c13cd5fedf2/Screenrecord-2018-07-28-14-53-15-925.mp4";
    Uri uri = Uri.parse(videoUrl2);
    try {
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        // 设置播放的视频源
        mediaPlayer.setDataSource(this, uri);
        // 设置显示视频的SurfaceHolder
        mediaPlayer.setDisplay(mSurfaceView.getHolder());
        Log.i(TAG, "开始装载");
        mediaPlayer.prepareAsync();
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

            @Override
            public void onPrepared(MediaPlayer mp) {
                Log.i(TAG, "装载完成");
                mediaPlayer.start();
                // 按照初始位置播放
                mediaPlayer.seekTo(msec);
                // 设置进度条的最大进度为视频流的最大播放时长
                mSeekBar.setMax(mediaPlayer.getDuration());
                // 开始线程,更新进度条的刻度
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            isPlaying = true;
                            while (isPlaying) {
                                // 如果正在播放,没0.5.毫秒更新一次进度条
                                int current = mediaPlayer.getCurrentPosition();
                                mSeekBar.setProgress(current);

                                sleep(500);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }.start();

                mBtnPlay.setEnabled(false);
            }
        });
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mp) {
                mp.release();
                mp = null;
                // 在播放完毕被回调
                mBtnPlay.setEnabled(true);
            }
        });

        mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                // 发生错误重新播放
                play(0);
                isPlaying = false;
                return false;
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}

常用SeekBar显示进度及改变进度

private SeekBar.OnSeekBarChangeListener change = new SeekBar.OnSeekBarChangeListener() {

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        // 当进度条停止修改的时候触发
        // 取得当前进度条的刻度
        int progress = seekBar.getProgress();
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            // 设置当前播放的位置
            mediaPlayer.seekTo(progress);
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress,
                                  boolean fromUser) {

    }
};

二、基于MediaPlayer和SurfaceView,Android还提供了VideoView和MediaController,方便用户使用。VideoView继承了SurfaceView,并且实现了MediaPlayerControl接口,其内成员变量MediaPlayer

public class ControllerActivity extends Activity {
    private VideoView vv_video;
    private MediaController mController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_controller);
        vv_video = (VideoView) findViewById(R.id.vv_video);
        // 实例化MediaController
        mController = new MediaController(this);
        showRemoteVideo();
    }

    private void showLocalVideo() {
        File file = new File("/storage/emulated/0/DCIM/Camera/VID_20180731_081115.mp4");
        if (file.exists()) {
            // 设置播放视频源的路径
            vv_video.setVideoPath(file.getAbsolutePath());
            // 为VideoView指定MediaController
            vv_video.setMediaController(mController);
            // 为MediaController指定控制的VideoView
            mController.setMediaPlayer(vv_video);
        }
    }

    private void showRemoteVideo() {
        String videoUrl2 = "http://edu.ismartwork.cn/v/lcl/ftpFile/data/workerCircle/cf7cc922de584d0f88833c13cd5fedf2/Screenrecord-2018-07-28-14-53-15-925.mp4";
        Uri uri = Uri.parse(videoUrl2);
        vv_video.setVideoURI(uri);
        vv_video.setMediaController(mController);
        vv_video.start();
    }
}

三、基于ExoPlayer

github:https://github.com/google/ExoPlayer

官网:http://google.github.io/ExoPlayer/

ExoPlayer是谷歌开源的专用于Android的应用级的多媒体播放器。Android框架提供的除MediaPlayer之外,还提供了些低等级的媒体API,例如:MediaCodec, AudioTrack,MediaDrm,可以用于建立自定义媒体播放。ExoPlayer支持一些Android自带的MediaPlayer现不支持的API,包括DASH(动态的自适应流HTTP)和SmoothStreaming adaptive playbacks(平滑流)。

翻译水平一般,直接上他人译文,

以下转自:https://www.jianshu.com/p/3251a5189f56及https://blog.youkuaiyun.com/u014606081/article/details/76181049

优点:

  • 支持通过HTTP(DASH)和SmoothStreaming进行动态自适应流,这两种都不受MediaPlayer的支持,还支持许多其他格式。
  • 能够自定义和扩展播放器,以适应各种不同需求。ExoPlayer专门设计了这一点,大部分组建都可以自己替换
  • 官网说了很多,其实说到底最主要的就是各个组件可以自定义,还可以接入ffmpeg组件,基本能满足99.9%的需求

缺点:ExoPlayer的音频和视频组件依赖Android的MediaCodec接口,该接口发布于Android4.1(API等级16),因此不得低于4.1

Library概述:

ExoPlayer库的核心是ExoPlayer接口,ExoPlayer公开了传统的高级媒体播放器功能,例如缓冲媒体,播放,暂停和seek等功能。在具体实现方面,该开源库对播放的媒体类型、存储方式、位置、渲染方式等进行了最少的实现,旨在让开发者自定义各种特性。ExoPlayer实现不是直接实现加载和呈现媒体,而是将这项工作委托给各种组件。 所有ExoPlayer共同的组件有:

  • MediaSource:定义多媒体数据源,这个类的功能就是从Uri中读取多媒体文件的二进制数据。 MediaSource在播放开始时通过ExoPlayer.prepare注入

  • TrackSelector:轨道提取器,从MediaSource中提取各个轨道的二进制数据,交给Renderer渲染。创建播放器时初注入

  • Renderer:对多媒体中的各个轨道(音轨、视频轨、字幕轨等)数据进行渲染,渲染就是“播放”,把二进制文件渲染成声音、画面。 创建播放器时注入

  • LoadControl:对MediaSource进行控制,比如什么时候开始缓冲、缓冲多少等等。创建播放器时注入

该库为提供了这些组件的默认实现,能够满足大部分需求,如果有特殊需求,可以通过自定义组件来实现。 比如,可以自定义LoadControl来更改播放器的缓冲策略,或自定义Renderer来渲染Android本身不支持的编解码器。

整个库中都存在注入组件的概念,许多子组件都能单独替换成自定义组件,而不会影响整个流程。 例如,默认的MediaSource实现需要通过其构造函数注入一个或多个DataSource工厂, 通过提供自定义Factory,可以从非标准源或不同的网络堆栈加载数据。

public class SimpleExoPlayerActivity extends Activity {

    private SimpleExoPlayer player;
    private PlayerView playerView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_exoplayer);

        playerView = findViewById(R.id.playerView);

        // 1. Create a default TrackSelector
        Handler mainHandler = new Handler();
        BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
        TrackSelection.Factory videoTrackSelectionFactory =
                new AdaptiveTrackSelection.Factory(bandwidthMeter);
        DefaultTrackSelector trackSelector =
                new DefaultTrackSelector(videoTrackSelectionFactory);

        // 2. Create the player
        player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
        playerView.setPlayer(player);

        // Measures bandwidth during playback. Can be null if not required.
        // DefaultBandwidthMeter bandwidthMeter1 = new DefaultBandwidthMeter();
        // Produces DataSource instances through which media data is loaded.
        DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
                Util.getUserAgent(this, "yourApplicationName"));
        // This is the MediaSource representing the media to be played.
        String videoUrl2 = "http://edu.ismartwork.cn/v/lcl/ftpFile/data/workerCircle/cf7cc922de584d0f88833c13cd5fedf2/Screenrecord-2018-07-28-14-53-15-925.mp4";
        Uri uri = Uri.parse(videoUrl2);
        MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
                .createMediaSource(uri);
        // Prepare the player with the source.
        player.prepare(videoSource);
        player.setPlayWhenReady(true);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (player != null) {
            player.release();
        }
    }
}

四、基于FFmpeg

官网:http://ffmpeg.org/

FFmpeg有很多个平台,需要用户进行编译,这里就直接介绍一下ijkPlayer,ijkPlayer是B站开源的基于FFmpeg的ffplay。

github:https://github.com/Bilibili/ijkplayer

。。。未完待续。。。

 

注:

1. TecentExoPlayer

原腾讯VOD点播框架基于一代的ExoPlayer,自定义TecentExoPlayer,实现了EventListener

com.google.android.exoplayer.MediaCodecVideoTrackRenderer.EventListener
public interface EventListener extends com.google.android.exoplayer.MediaCodecTrackRenderer.EventListener {
    void onDroppedFrames(int var1, long var2);

    void onVideoSizeChanged(int var1, int var2, float var3);

    void onDrawnToSurface(Surface var1);
}

1). onVideoSizeChanged方法,float参数传递了一个宽高比,通过这个宽高比,知道视频本身是竖屏还是横屏的,但是我们上传的视频本身确实width和height是反的呀,改这里没法解决。。。

2). 注意到TencentExoPlayer中有个接口是InfoListener,其中方法onVideoFormatEnabled

public interface InfoListener {
    void onVideoFormatEnabled(Format var1, int var2, int var3);

    void onAudioFormatEnabled(Format var1, int var2, int var3);

    void onDroppedFrames(int var1, long var2);

    void onBandwidthSample(int var1, long var2, long var4);

    void onLoadStarted(int var1, long var2, int var4, int var5, Format var6, int var7, int var8);

    void onLoadCompleted(int var1, long var2, int var4, int var5, Format var6, int var7, int var8, long var9, long var11);

    void onDecoderInitialized(String var1, long var2, long var4);

    void onSeekRangeChanged(TimeRange var1);
}

想通过这种方式应该能获取到Format中旋转角度参数,而VideoRootFrame中设置所有的实现都是通过EventLogger

VideoRootFrame:

this.eventLogger = new EventLogger();
this.eventLogger.startSession();
this.player.addListener(this.eventLogger);
this.player.setInfoListener(this.eventLogger);
this.player.setInternalErrorListener(this.eventLogger);

EventLogger:

public void onVideoFormatEnabled(Format format, int trigger, int mediaTimeMs) {
    Log.d("EventLogger", "videoFormat [" + this.getSessionTimeString() + ", " + format.id + ", " + Integer.toString(trigger) + "]");
}

运行,试试Format中的值是否含旋转信息:

08-21 16:51:04.776 4692-4692/com.ygsoft.study.ygedu D/EventLogger: start [0]
08-21 16:51:04.778 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [0.00, false, P]
08-21 16:51:04.787 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [0.01, true, I]
08-21 16:51:04.853 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [0.08, true, P]
08-21 16:51:06.954 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [2.18, true, B]
08-21 16:51:07.226 4692-4692/com.ygsoft.study.ygedu D/EventLogger: decoderInitialized [2.45, OMX.qcom.video.decoder.avc]
08-21 16:51:07.264 4692-4692/com.ygsoft.study.ygedu D/EventLogger: decoderInitialized [2.49, OMX.google.aac.decoder]
08-21 16:51:07.321 4692-4692/com.ygsoft.study.ygedu D/EventLogger: videoSizeChanged [362, 640, 1.0]
08-21 16:51:07.361 4692-4692/com.ygsoft.study.ygedu D/EventLogger: state [2.58, true, R]

白高兴一场了,根本没执行:-(,找到TencentExoPlayer:

public void onDownstreamFormatChanged(int sourceId, Format format, int trigger, int mediaTimeMs) {
    if(this.infoListener != null) {
        if(sourceId == 0) {
            this.videoFormat = format;
            this.infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs);
        } else if(sourceId == 1) {
            this.infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs);
        }

    }
}

找了一通源码,只有HlsSampleSource中有调用:

private void notifyDownstreamFormatChanged(final Format format, final int trigger, final long positionUs) {
    if(this.eventHandler != null && this.eventListener != null) {
        this.eventHandler.post(new Runnable() {
            public void run() {
                HlsSampleSource.this.eventListener.onDownstreamFormatChanged(HlsSampleSource.this.eventSourceId, format, trigger, HlsSampleSource.this.usToMs(positionUs));
            }
        });
    }

}

而VideoRootFrame中定义:

private RendererBuilder getRendererBuilder() {
    String userAgent = Util.getUserAgent(this.context, "ExoPlayerDemo");
    switch($SWITCH_TABLE$com$tencent$qcload$playersdk$util$VideoInfo$VideoType()[this.contentType.ordinal()]) {
    case 1:
        return new HlsRendererBuilder(this.context, userAgent, this.contentUri.toString(), this.audioCapabilities);
    case 2:
        return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new Mp4Extractor());
    case 3:
        return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new Mp3Extractor());
    case 4:
        return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new AdtsExtractor());
    case 5:
        return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new FragmentedMp4Extractor());
    case 6:
    case 7:
        return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new WebmExtractor());
    case 8:
        return new ExtractorRendererBuilder(this.context, userAgent, this.contentUri, new TsExtractor(0L, this.audioCapabilities));
    default:
        throw new IllegalStateException("Unsupported type: " + this.contentType);
    }
}

我们这里是MP4,用的是ExtractorRendererBuilder,没有调用HlsRenderBuilder,所以没有执行,直接改实现,又担心其他格式的视频播放出问题,就没有继续了。。。

2. SimpleExoPlayer

SimpleExoPlayer,二代的ExoPlayer实现,估计那时候ExoPlayer还没有更新到2.x版本,SimpleExoPlayer实现了VideoListener,里面onVideoSizeChanged方法含能够将unappliedRotationDegrees

/** A listener for metadata corresponding to video being rendered. */
public interface VideoListener {

  /**
   * Called each time there's a change in the size of the video being rendered.
   *
   * @param width The video width in pixels.
   * @param height The video height in pixels.
   * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise
   *     rotation in degrees that the application should apply for the video for it to be rendered
   *     in the correct orientation. This value will always be zero on API levels 21 and above,
   *     since the renderer will apply all necessary rotations internally. On earlier API levels
   *     this is not possible. Applications that use {@link android.view.TextureView} can apply the
   *     rotation by calling {@link android.view.TextureView#setTransform}. Applications that do not
   *     expect to encounter rotated videos can safely ignore this parameter.
   * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
   *     square pixels this will be equal to 1.0. Different values are indicative of anamorphic
   *     content.
   */
  void onVideoSizeChanged(
      int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio);

  /**
   * Called when a frame is rendered for the first time since setting the surface, and when a frame
   * is rendered for the first time since a video track was selected.
   */
  void onRenderedFirstFrame();
}

另外SimpleExoPlayer中成员变量有个videoFormat,Format类型,含rotationDegrees属性,源码是这样注释的:

/**
 * The clockwise rotation that should be applied to the video for it to be rendered in the correct
 * orientation, or 0 if unknown or not applicable. Only 0, 90, 180 and 270 are supported.
 */
public final int rotationDegrees;

所以利用SimpleExoPlayer和PlayerView是可以简单解决之前录像视频旋转的问题,但是这又没缓存,非常耗流量,界面也不太好看,赶时间,就选用了现腾讯VOD点播的API

3. 现腾讯云点播API,基于ffmpeg

Android进阶:十、自定义视频播放器-1 简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。 阅读详情

相关推荐

Android视频播放器

Android实现视频和音频播放 2021-5-26 (一)、java文件 1、MovieActivity package com.example.player; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Environment; import android.view.View; import android.widget.Button; impor

Biewenwsm的博客 641

Android自定义音视频播放器

自定义View播放器已经实现了,但我们需要掌握MediaPlayer的初始化过程是怎么样的,MediaPlayer做了哪些工作,音视频数据如何渲染到SurfaceView上面的,这需要把MediaPlayer框架原理搞清楚。随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时做为Android研发的你,想要提升本身的能力还不知道怎么开发视频播放器怎么行?等等,这些部分的播放器我们在开发过程中,使用过这些视频播放框架来播放本地视频或者网络视频,但不一定会满足业务需求的。

qq_31481093的博客 2333

Android进阶:十、自定义视频播放器-1,面试安卓系统架构

随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:SmallVideoPlayer ###一.需求分析 我们观察一个视频播放器,可以看到视频播放器除了正在播放的视频还有很多控件,比如播放按钮,暂停按钮,播放进度条,播放计时器等。 这么多控件显然无法播放视频,但是他们都在控制视频的播放。由此可见视频播放器可以分为两层,一层为视频播放器控制层,一层为真正的视频播放层。.

m0_64603776的博客 1848

Android 视频播放器dkplayer

【代码】Android 视频播放器dkplayer。

qq_19688207的博客 4382

Android自定义视频播放器

在我的博客Android控件–VideoView中介绍了如何用VideoView去播放本地视频和网络视频,不过我们播放视频是调用的是系统给我们的UI界面,所以这篇博客是介绍如何去定制自己的播放器。我们的UI布局就是实现成这个样子,在横屏的时候有音量的显示,竖屏的时候则会隐藏。另外还可以在画面上滑动实现音量和亮度的变化。我们先来实现布局样式。<RelativeLayout xmlns:android=

HardWorkingAnt的博客 4975

Android进阶 自定义视频播放器

随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:SmallVideoPlayer 一.需求分析 我们观察一个视频播放器,可以看到视频播放器除了正在播放的视频还有很多控件,比如播放按钮,暂停按钮,播放进度条,播放计时器等。 这么多控件显然无法播放视频,但...

beibei_programme的博客 518

Android系列之自定义视频播放器

自定义视频播放器自述xml部分Java对面部分需要使用到的Animation资源需要自定义的工具类 自述 自己写的一个简易视频播放器,如果有需要的话欢迎参考和转载,但拒绝抄袭,谢谢理解与配合。 转载地址: xml部分 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/re

weixin_43661434的博客 1066

Android 视频播放器之JieCaoVideoPlayer的简单实用以及自定义UI

最近在做视频播,项目不是专门的视频播放器类软件,其中有小窗口视频播放功能,之前做的看了下好多bug,更有个严重问题,视频拉伸严重,和不能全屏切换全屏播放(全屏播放需求暂时没有提,但我肯定老板后续肯定会提出这个需求的)。看着之前人写的一个类里2000多行代码又是各种的嵌套自定义接口瞬间觉得为了自己还是打算重新定义个视频播放器来的好点。 这里说下JieCaoVideoPlayer主要特点视频全屏播放

帅气大果果 1万+

Android进阶:十、自定义视频播放器 1

随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:SmallVideoPlayer 一.需求分析 我们观察一个视频播放器,可以看到视频播放器除了正在播放的视频还有很多控件,比如播放按钮,暂停按钮,播放进度条,播放计时器等。 这么多控件显然无法播放视频,但...

Android_SE的博客 597

Android中自定义VideoView视频播放器

首先我们最简单的实现VideoView视频播放功能 通过VideoView播放视频的步骤:  1、在界面布局文件中定义VideoView组件,或在程序中创建VideoView组件  2、调用VideoView的如下两个方法来加载指定的视频          setVidePath(String path):加载path文件代表的视频          setVideo

Zzq_Big_Cow的博客 2990

Android进阶:自定义视频播放器开发(上)

随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:SmallVideoPlayer 需求分析 我们观察一个视频播放器,可以看到视频播放器除了正在播放的视频还有很多控件,比如播放按钮,暂停按钮,播放进度条,播放计时器等。 这么多控件显然无法播放视频,但是他...

beibei_programme的博客 1115

Android自定义视频播放器(三)

参看:Android自定义视频播放器(一):https://blog.youkuaiyun.com/zxd1435513775/article/details/81507909 参看:Android自定义视频播放器(二):https://blog.youkuaiyun.com/zxd1435513775/article/details/81533053 一、引言...

scorpio的博客 9079

Android源码实现自定义视频播放器

为了实现自定义的缓冲策略,我们可以在视频播放器的基础上增加一些控制逻辑。下面是一个简化的自定义缓冲策略的实现示例:// 假设 player 是一个视频播放器的实例// 自定义的缓冲策略类// 控制缓冲行为的方法// 获取当前播放器状态和网络速度等信息// 根据状态和速度决定缓冲策略// 初始缓冲// 初始缓冲5秒// 网速慢时增加缓冲// 动态缓冲10秒// 暂停时保持当前缓冲或稍作减少// 维持缓冲2秒// 获取网络速度的方法(此处为示例,实际实现需要根据具体情况)

weixin_42613017的博客 975

Android进阶:自定义视频播放器开发(下)

分享一份NDK基础开发资料分享内容包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!Flutter等全方面的Android进阶实践技术;

2401_84415534的博客 247

ZYNQ实战:PS端驱动DMA实现高效数据流转与验证

本文详细介绍了在ZYNQ平台上通过PS端驱动DMA实现高效数据流转的实战方法。从ZYNQ的PS与PL数据交互基础出发,深入解析DMA核心价值,包括硬件平台搭建、Vivado配置、PS端软件架构设计及性能优化技巧,帮助开发者提升数据传输效率与系统性能。

weixin_29192141的博客 436

RK3576开发板ROS2部署实战:从Buildroot配置到Hello World Demo

本文详细介绍了在RK3576开发板上部署ROS2的完整实战流程。从Buildroot工程配置、集成ROS2依赖,到搭建Docker交叉编译环境、处理编译难题,最终完成系统镜像打包并在板端成功运行Talker-Listener Demo。该指南旨在帮助开发者深入理解嵌入式机器人系统的构建链,实现从底层到应用的完全掌控。

mac99的博客 1156

Android自定义视频播放器(四)

参看:Android自定义视频播放器(一):https://blog.youkuaiyun.com/zxd1435513775/article/details/81507909 参看:Android自定义视频播放器(二):https://blog.youkuaiyun.com/zxd1435513775/article/details/81533053 参看:Android自定义视频播放器(三):https://bl...

scorpio的博客 1484

Android 音频源码分析——AudioTrack设备选择

Android 音频源码分析——AndroidRecord录音(一) Android 音频源码分析——AndroidRecord录音(二) Android 音频源码分析——AndroidRecord音频数据传输流程 Android 音频源码分析——audioserver启动 Android 音频源码分析——AudioFlinger Android 音频源码分析——AudioTrack设备选择 基于Andorid9.0源码 以AudioTrack为例,梳理下输出设备选择流程。 音频设备选择的影响因素: Au

奋斗的菜鸟ing 3971

android surfaceView+mediaPlayer 自定义视频播放器

Android 视频播放一般分为两种: 1.使用Android自带播放器 VideoView ,其中一些功能,已经都给实现好了(此view一般不满足自己的需求,比如:界面啊,控制啊,以及可能还有手势控制音量,手势控制亮度等等,那么就会选择第二种方式) 2.surfaceView+mediaPlayer 自定义播放器(这个可以制作自己想要任何功能以及界面的播放器,当然前提得自己实现) 主要就是

duke618的博客 4734

【WorkBuddy】WorkBuddy 智能体快速上手:从零到一的实践指南

文章摘要: 传统自动化脚本难以应对灵活多变的自然语言需求,导致逻辑复杂、维护成本高。智能体(Agent)通过理解语义意图、动态决策和松耦合组件解决了这些问题。WorkBuddy采用声明式配置,通过可视化界面定义意图、实体和对话策略,无需编码即可快速构建智能体。其核心组件包括意图分类、实体提取、对话策略、故事样本和回退机制,适用于客服支持、知识问答等高频场景。通过环境准备(Python/Node.js/Redis)和简单配置,用户可以快速搭建可运行的智能体,显著提升自动化效率。

ruiqideng的博客 1646

别再死记硬背公式了!用ADS手把手教你仿真一个4GHz镜像抑制混频器(附完整工程文件)

本文详细介绍了使用Keysight ADS软件设计和仿真4GHz镜像抑制混频器的完整流程。从理论分析到实际工程实现,涵盖分支线耦合器设计、二极管建模、噪声系数仿真及性能优化等关键步骤,帮助工程师摆脱公式死记硬背,掌握实用的射频电路设计技能。附完整工程文件,可直接应用于实际项目开发。

weixin_28683391的博客 496
上一篇: Android与H5交互
下一篇: 分辨率、帧率和码率三者之间的关系
leverge2009
博客等级 码龄14年 34粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值