【Android 前沿技术】用MediaPlayer+TextureView封装好的视频播放器,可直接使用(附demo)

【HDR学习】利用TextureView显示图片 demo场景:利用textureView实现图片显示,并更改其色彩空间(sRGB和DCI_P3)拼接显示。 阅读详情

一、引言

在经过将近半个月的调研开发,我终于算是对“Android短视频开发”入门了。为了给自己一个总结,也为了把自己潜心研究的成果分享出来,我决定写下这篇blog。

播放器已经封装好,结构很清晰,直接调用即可。这里附上效果图和demo(点击下载)!

 

其中,图一为全屏播放,图二为播放中,图三为暂停,图四为上滑展示小窗口,图五为网络异常提示。

    

 

二、方案选择

1、方案一:videoView+mediaPlayer

videoView继承自SurfaceView。surfaceView是在现有View上创建一个新的Window,内容显示和渲染是在新的Window中,这使得SurfaceView的绘制和刷新可以在单独的线程中进行。由于SurfaceView的内容是在新建的Window中,这使得SurfaceView不能放在RecyclerView或ScrollView中,一些View中的特性也无法使用。

 

2、方案二:textureView+mediaPlayer

textureView不会创建新的窗口,它的使用跟其他普通View一样。

 

考虑到以后的可扩展性,最终采用方案二。

 

三、TextureView介绍

1、TextureView被创建后不能直接使用,必须将其添加到ViewGroup中。

2、TextureView必须要等SurfaceTexture准备就绪才能起作用,这里通常需要给TextureView设置监听器SurfaceTextureListener。等待onSurfaceTextureAvailable回调后,才能使用。

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        //SurfaceTexture准备就绪
        if (mSurfaceTexture == null) {
            mSurfaceTexture = surfaceTexture;
            openMediaPlayer();
        } else {
            mTextureView.setSurfaceTexture(mSurfaceTexture);
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
        //SurfaceTexture缓冲大小变化
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        //SurfaceTexture即将被销毁
        return mSurfaceTexture == null;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        //SurfaceTexture通过updateImage更新
    }

SurfaceTexture的各个变化都有相应的回调方法。其中最重要的是onSurfaceTextureAvailable方法,该方法在surfaceTexture创建好后回调。主要在该方法中关联MediaPlayer,作为视频数据的图像来源。

 

四、MediaPlayer介绍

1、几种重要的状态

idle:空闲状态。当mediaPlayer没有prepareAsync之前,就是处于idle状态。

prepared:准备好状态。想要让mediaPlayer开始播放,不能直接start,必须要先prepareSync。这期间mediaPlayer会一直在准备preparing,直到进入prepared状态。

started:当mediaPlayer准备好,就可以调用mediaPlayer的start方法进入started状态。

paused:当调用pause方法,进入paused状态。

completed:播放完成,进入completed状态。

error:播放错误。

 

2、几个重要的方法

prepareAsync:要想使用mediaPlayer,必须先调用prepareAsync。这是第一步。

start:开始

pause:暂停

reset:播放完成后,如想重新开始,调用该方法。

 

3、几个重要的回调

onSurfaceTextureAvailable:开始关联mediaPlayer

onPrepared:此处开始调用mediaPlayer.start()

onInfo:播放开始后,视频到底状态如何,就是在onInfo中处理

 

    @Override
    public boolean onInfo(MediaPlayer mp, int what, int extra) {
        if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
            // 播放器渲染第一帧
            mCurrentState = STATE_PLAYING;
            mController.onPlayStateChanged(mCurrentState);
        } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
            // MediaPlayer暂时不播放,以缓冲更多的数据
            if (mCurrentState == STATE_PAUSED || mCurrentState == STATE_BUFFERING_PAUSED) {
                mCurrentState = STATE_BUFFERING_PAUSED;
            } else {
                mCurrentState = STATE_BUFFERING_PLAYING;
            }
            mController.onPlayStateChanged(mCurrentState);
        } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
            // 填充缓冲区后,MediaPlayer恢复播放/暂停
            if (mCurrentState == STATE_BUFFERING_PLAYING) {
                mCurrentState = STATE_PLAYING;
                mController.onPlayStateChanged(mCurrentState);
            }
            if (mCurrentState == STATE_BUFFERING_PAUSED) {
                mCurrentState = STATE_PAUSED;
                mController.onPlayStateChanged(mCurrentState);
            }
        } else {
            LogUtil.d("onInfo ——> what:" + what);
        }
        return true;
    }

 

五、封装自己的短视频播放器

以上是短视频开发的知识点,如果是初识视频开发,可能会不清楚讲的是什么,可以接着看下面的视频播放器的封装。这里会详细地介绍如何利用TextureView和MediaPlayer封装一个属于自己的视频播放器。由于MediaPlayer的方法和状态比较多,播放暂停操作在不同状态下应调用不同方法。

一个短视频播放控件应该包含两层:顶层是播放器的控制器mController,底层是播放视频内容的TextureView。这里将这两层封装在一个容器FrameLayout中。

    public TourVideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        if (mNetworkChangeReceiver == null) {
            mNetworkChangeReceiver = new NetworkChangeReceiver(this);
        }
        allow4GFlag = false;
        init();
    }

    private void init() {
        mContainer = new FrameLayout(mContext);
        mContainer.setBackgroundColor(Color.BLACK);
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        this.addView(mContainer, params);
    }

 

    private void addTextureView() {
        mContainer.removeView(mTextureView);
        LayoutParams params = new LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT,
                Gravity.CENTER);
        mContainer.addView(mTextureView, 0, params);
    }
    public void setController(IVideoController controller) {
        mContainer.removeView(mController);
        mController = controller;
        mController.reset();
        mController.setVideoPlayer(this);
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        mContainer.addView(mController, params);
    }

触发播放是,将TextureView、MediaPlayer、Controller进行初始化。待TextureView的数据通道SurfaceTexture准备就绪后,打开播放器。

    private void openMediaPlayer() {
        // 屏幕常亮
        mContainer.setKeepScreenOn(true);
        // 设置监听
        mMediaPlayer.setOnPreparedListener(this);
        mMediaPlayer.setOnVideoSizeChangedListener(this);
        mMediaPlayer.setOnCompletionListener(this);
        mMediaPlayer.setOnErrorListener(this);
        mMediaPlayer.setOnInfoListener(this);
        mMediaPlayer.setOnBufferingUpdateListener(this);
        mCurrentNetworkState = NetworkChangeReceiver.getNetworkStatus(CtripBaseApplication.getInstance());
        // TODO: 2018/1/4 待确定
        mNetworkChangeReceiver.registerNetworkChangeBroadcast();

        // 设置dataSource
        try {
            mMediaPlayer.setDataSource(mUrl);
            if (mSurface == null) {
                mSurface = new Surface(mSurfaceTexture);
            }
            mMediaPlayer.setSurface(mSurface);
            mMediaPlayer.prepareAsync();
            mCurrentState = STATE_PREPARING;
            mController.onPlayStateChanged(mCurrentState);
        } catch (IOException e) {
            e.printStackTrace();
            LogUtil.e("打开播放器发生错误", e);
        }
    }
    private void initMediaPlayer() {
        if (mMediaPlayer == null) {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        }
    }

    private void initTextureView() {
        if (mTextureView == null) {
            mTextureView = new TourTextureView(mContext);
            mTextureView.setSurfaceTextureListener(this);//此时回调onSurfaceTextureAvailable
        }
    }
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        if (mSurfaceTexture == null) {
            mSurfaceTexture = surfaceTexture;
            openMediaPlayer();
        } else {
            mTextureView.setSurfaceTexture(mSurfaceTexture);
        }
    }

播放逻辑写完之后,具体UI展示逻辑在VideoPlayerController中。根据不同的状态,VideoPlayerController展示不同UI。

    public static final int STATE_ERROR = -1;               //播放错误
    public static final int STATE_IDLE = 0;                 //播放未开始
    public static final int STATE_PREPARING = 1;            //播放准备中
    public static final int STATE_PREPARED = 2;             //播放准备就绪
    public static final int STATE_PLAYING = 3;              //正在播放
    public static final int STATE_PAUSED = 4;               //暂停播放
    public static final int STATE_BUFFERING_PLAYING = 5;    //正在缓冲
    public static final int STATE_BUFFERING_PAUSED = 6;     //正在缓冲 播放器暂时
    public static final int STATE_COMPLETED = 7;            //播放完成
    public static final int STATE_NOTE_4G = 8;              //提示4G
    public static final int STATE_NOTE_DISCONNECT = 9;      //提示断网

    public static final int MODE_NORMAL = 10;               //普通模式
    public static final int MODE_FULL_SCREEN = 11;          //全屏模式
    public static final int MODE_TINY_WINDOW = 13;          //小窗口模式

全屏、小窗口播放的实现

如何实现全屏?

将mContainer移除,并添加到android.R.content中,并设置成横屏

    @Override
    public void enterFullScreen() {
        if (mCurrentMode == MODE_FULL_SCREEN) return;

        // 隐藏ActionBar、状态栏,并横屏
        TourVideoUtil.hideActionBar(mContext);
        TourVideoUtil.scanForActivity(mContext)
                .setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        new Handler().post(new Runnable() {
            @Override
            public void run() {
                ViewGroup contentView = (ViewGroup) TourVideoUtil.scanForActivity(mContext)
                        .findViewById(android.R.id.content);
                if (mCurrentMode == MODE_TINY_WINDOW) {
                    contentView.removeView(mContainer);
                } else {
                    TourVideoPlayer.this.removeView(mContainer);
                }
                LayoutParams params = new LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT);
                contentView.addView(mContainer, params);
            }
        });

        mCurrentMode = MODE_FULL_SCREEN;
        mController.onPlayModeChanged(mCurrentMode);
    }

如何实现小窗口?

将mContainer移除,添加到android.R.content中,并设置宽高

    @Override
    public void enterTinyWindow() {
        if (mCurrentMode == MODE_TINY_WINDOW) return;
        this.removeView(mContainer);

        new Handler().post(new Runnable() {
            @Override
            public void run() {
                ViewGroup contentView = (ViewGroup) TourVideoUtil.scanForActivity(mContext)
                        .findViewById(android.R.id.content);
                // 小窗口的宽度为屏幕宽度的60%,长宽比默认为16:9,右边距、下边距为8dp。
                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                        (int) (CommonUtil.getScreenWidth(mContext) * 0.6f),
                        (int) (CommonUtil.getScreenWidth(mContext) * 0.6f * 9f / 16f));
                params.gravity = Gravity.TOP | Gravity.START;
                params.topMargin = CommonUtil.dp2px(mContext, 48f);

                contentView.addView(mContainer, params);
            }
        });

        mCurrentMode = MODE_TINY_WINDOW;
        mController.onPlayModeChanged(mCurrentMode);
    }

 

六、最后

由于日常工作繁忙,这里博客写的可能没有很详细,不过demo写的非常详细,应该会是你想要的~~~~

待日后有时间时,我会进一步优化一下。

有任何问题,欢迎留言或邮件联系zhshan@ctrip.com

最后再次附上demo。



 

 

 

 

 

Android列表嵌入视频播放 Android列表中嵌入视频播放,当Item消失时会自动停止正在播放的视频 阅读详情

相关推荐

安卓网络视频播放器源码拆解:从MediaPlayer到ExoPlayer实战指南

网络视频播放器是安卓开发中常见的复杂模块,涉及数据源解析、播放内核调度、UI交互与生命周期绑定等多层技术。理解MediaPlayer的状态机与Surface渲染时序,是排查黑屏、卡顿等问题的关键;而ExoPlayer等现代播放内核则提供了更强的流媒体适配与缓冲控制能力。无论是改造既有源码还是从零构建,掌握视频源加载、错误回调处理、横竖屏适配等工程实践,都能显著提升应用的稳定性和体验。本文以一份典型的“安卓网络视频播放器源码”为样本,梳理其目录结构、核心模块与播放链路,并给出二次开发中的常见坑位与优化路径,帮

weixin_32496547的博客 254

Android使用SurfaceView和TextureView来播放视频

最近项目中在调试一个问题:app端呈现摄像头画面的调整,很坑的一个问题。项目经理估计搞不定,就把这个问题扔给我了,有点坑啊。因为播放摄像头视频的控件是继承于surfaceview的,于是乎就从surfaceview入手,因为这个控件是继承于surfaceview,又稍微做了些封装,结果机智的我就直接写了如下代码: ViewGroup.LayoutParams params = surfaceView

小飞哥的专栏 6907

Android原生网络视频播放器开发实战

本文还有配套的精品资源,点击获取 简介:在Android开发中,实现支持网络资源播放的原生视频播放器是一项常见且重要的任务。本文基于Android自带的MediaPlayer类,系统讲解了如何构建一个功能完整的视频播放器,涵盖从网络加载视频、预加载准备、播放控制到资源释放的全流程。通过结合VideoView或SurfaceView进行画面显示,并集成事件监听与错误处理机制,确保播放过程稳定流畅。同时,文章还探讨了性能优化策略如本地缓存、兼容性适配以及使用ExoPlayer等第三方

weixin_30661119的博客 674

MediaPlayer+TextureView实现视频播放功能

3、初始化TextureView,并设置监听,需求需要给给TextureView加一个圆角,角度是16。2、初始化视频资源,这里把需要播放的视频集成在应用中,位置放在res/raw下。前面写一些基础知识的学习,这篇写个小demo,实现视频循环播放功能。这个demo只是简单实现,具体播放最好放子线程进行。4、初始化MediaPalyer播放器。

孔小乐的博客 1330

安卓网络视频播放器源码拆解:从ExoPlayer选型到二次开发实战

在Android应用开发中,视频播放功能是许多App的核心模块之一。开发者面对网络视频播放器源码时,往往需要理解从传输协议到解码渲染的完整链路。HLS、RTSP、RTMP等流媒体协议决定了数据源如何喂给解码器,而MediaPlayer、ijkplayer与谷歌官方ExoPlayer(现归属于Media3)的选型则直接影响兼容性与扩展能力。掌握ExoPlayer的缓冲策略、自适应码率、SurfaceView与TextureView的取舍,能够有效解决播放黑屏、延迟卡顿和生命周期等问题。无论是点播场景还是直播低

weixin_29181861的博客 268

Android视频播放器实战:硬解适配、Surface绑定与后台续播

Android视频播放器本质是多媒体系统与硬件驱动的深度协同,其核心在于理解MediaCodec硬解原理、Surface生命周期管理及音频焦点机制。硬解失败常源于厂商HAL兼容性差异,需结合MediaCodecList检测与动态降级策略;Surface绑定若错失GPU显存就绪时机,将导致黑屏或ANR;而AudioFocus未分级处理则引发锁屏静音、音画不同步等产线高频问题。本文聚焦ExoPlayer 2.19+与MediaPlayer双后端工程实践,覆盖H.265/AV1软硬解适配、TextureView/

weixin_31476015的博客 110

MediaPlayer+TextureView,完美切换全屏、小窗口的 Android 视频播放器

NiceVieoPlayer 项目地址:xiaoyanger0825/NiceVieoPlayer  简介:MediaPlayer+TextureView,完美切换全屏、小窗口的 Android 视频播放器    Features 用 IjkPlayer/MediaPlayer + TextureView 封装,可切换 IjkPlayer、MediaPlayer. 支持本地和网络视频播...

未来很长,但我会努力的走下去。 9558

Android视频播放器稳定性实战:ExoPlayer硬解适配与降级策略

Android视频播放器本质是多媒体子系统与硬件解码能力的深度协同,其核心原理在于MediaCodec硬解调度、Surface生命周期管理及Renderer管道编排。技术价值体现在首帧延迟控制、卡顿率压降与跨OEM芯片兼容性保障。典型应用场景覆盖HLS/MP4流媒体播放、HDR视频渲染、后台音频续播及弱网自适应。本文聚焦ExoPlayer v2.19+生产级落地,深入解析硬解fallback机制、SurfaceView/TextureView绑定时机、华为/小米芯片专项适配,并提供可埋点、可降级、可诊断的播

weixin_27014595的博客 174

Android 视频播放

111

未来很长,但我会努力的走下去。 2847

Android Camera TextureView_demo 拍照 预览

android camera demo; 在android 6.0,7.0上,就是有时会出现没有预览的情况。一般都是因为Camera已经打开,而View 还没有初始化完成。在楼主的Demo上,只需要把开启Camera的代码放到CameraTextureView中的onSurfaceTextureAvailable就可以了

Android中使用TextureView播放视频

Android使用SurfaceView播放视频不能使用变换,本文教你如何在Android中使用TextureView播放视频平移、缩放、旋转设置透明度

android studio多媒体播放器,Android支持全屏、小窗口的视频播放器

NiceVieoPlayerAndroid支持全屏、小窗口的视频播放器,完美实现全屏、小窗口播放切换Features用IjkPlayer/MediaPlayer + TextureView封装,可切换IjkPlayer、MediaPlayer.支持本地和网络视频播放.完美切换小窗口、全屏,可在RecyclerView中无缝全屏.手势滑动调节播放进度、亮度、声音.支持清晰度切换.可自定义控制界...

weixin_42513944的博客 3663

使用 ArcGIS Pro 和 Google Earth Engine 可视化地表温度

在这项研究中,利用 Landsat 热数据通过各种方法检查了 2013 年和 2023 年恰纳卡莱省的地表温度变化。使用了 NDVI、大气层顶部、亮度温度、植被比例和地表温度等公式。研究结果表明,从热图像中获得的数据,特别是地表温度(LST),是土地解释的重要资源。恰纳卡莱 — 土耳其(土耳其)使用 ArcGIS 程序和 Google Earth Engine 对不同季节的卫星图像进行分析。

gis收藏家的博客 1998

Android 平台视频播放器demo

1、简介 android-jungle-mediaplayer是 Android 平台上一款多媒体播放/语音录制的库。使用它你可以很方便的实现语音、视频播放功能。有以下优点: 功能齐备,可自定制播放器、播放组件; 内置实现了一个播放器,可以不加修改使用; 视频播放支持SurfaceView/TextureView渲染; 提供语音录制组件。 内置播放器支持以下功能: 手势(双击暂停...

yongyooh的专栏 1461

ADS笔记,新旧两组仿真数据进行绘图和列表对比

原理图器件参数的不同,怎么进行对比观看,操作如下。

彭辉荣的博客 3869

【专业书1】量化交易——从入门到精通

股票季节性策略几乎失效了,但商品期货的仍有效,因为其季节性需求源于真实的经济需求而非投机。交易策略获利的前提:交易价格处于均值回归(低价时买入)或动量状态(降价时先做空后以更低价买入),否则在随机游走状态下是无法有效交易的。构建交易策略实际上是在决定:在特殊条件、特定时间尺度下价格趋势的呈现(均值回归/趋势走势),以及用什么价格作为初始判断的参考价格。如果以高夏普比率为目标,需要更高的交易频率:基于大数定律,押注越多,偏离均值收益的偏差就越小。买同比最好的股票,卖同比最差的股票。但在不同的时间尺度下可以。

weixin_44589682的博客 1547

Android视频播放器开发实战:MediaPlayer与ExoPlayer封装指南

视频播放器是Android应用开发中常见的复杂场景,涉及解码、渲染、生命周期和交互等多层技术。MediaPlayer作为系统内置方案,适合本地播放和快速验证;ExoPlayer则提供强大的自适应流媒体支持,适合网络视频和自定义扩展。理解两者的工作原理,能帮助开发者合理选型,并构建稳定的播放内核。在实际工程中,播放器封装、UI布局和生命周期同步是用户体验的关键,例如通过TextureView实现灵活画面渲染,用Handler控制进度更新,并在Activity销毁时正确释放资源。无论是实现手势控制还是多视频切换

weixin_29189987的博客 264

PPT高清导出秘籍:如何轻松设置600dpi图片分辨率

本文详细解析了PPT导出图片模糊的根本原因,并提供了通过修改Windows注册表将导出分辨率从默认的96dpi提升至600dpi的完整教程。该方法能显著提升图片清晰度,满足学术出版与高质量印刷需求,文中还包含效果对比、进阶设置及常见问题解答。

a0b1c2d3的专栏 1316

Android视频播放器开发实战:Java实现MediaPlayer与生命周期管理

在Android应用开发中,视频播放功能是常见的核心需求之一。理解播放器的底层原理,往往比直接套用现成库更重要。MediaPlayer作为系统级播放组件,其状态机模型和异步准备机制决定了播放流程的基本框架。开发者还需关注Surface与TextureView的选择、进度同步、异常恢复等细节,并结合Activity生命周期做好资源释放,避免内存泄漏与黑屏问题。从本地视频扫描到列表展示,再到播放控制与手势扩展,一种清晰的分层设计能让后续迁移到ExoPlayer等高级播放器时更加平滑。本文基于实际工程经验,梳理从

weixin_42676824的博客 189

WKWebView 白屏问题,WKWebView request body丢失问题

1,WKWebView 白屏问题 WKWebView 自诩拥有更快的加载速度,更低的内存占用,但实际上 WKWebView 是一个多进程组件,Network Loading 以及 UI Rendering 在其它进程中执行。初次适配 WKWebView 的时候,我们也惊讶于打开 WKWebView 后,App 进程内存消耗反而大幅下降,但是仔细观察会发现,Other Process 的内存占用会...

weixin_37543822的博客 736

2025年“区块链技术”职业院校技能大赛赛后复盘与总结(必看)

记录一次2025年“区块链技术”职业院校技能大赛的参加经验总结与赛后复盘

我是网络安全兼技能大赛工程师,专注网络安全技术学习与技能大赛实战!持续分享最新的技术文章,帮你少走弯路,一起在技术赛道上进步~ 1992

android 类似 无缝全屏切换,NiceVieoPlayer: https://github.com/jianjunxiao/NiceVieoPlayer.git IjkPlayer/Media...

NiceVieoPlayer Features用IjkPlayer/MediaPlayer + TextureView封装,可切换IjkPlayer、MediaPlayer.支持本地和网络视频播放.完美切换小窗口、全屏,可在RecyclerView中无缝全屏.手势滑动调节播放进度、亮度、声音.支持清晰度切换.可自定义控制界面.Usage下载niceviewoplayer库,在AndroidStu...

weixin_42360905的博客 832

100天精通Python(数据分析篇)——第53天:初识pandas模块_基础介绍

一、初始pandas 1. 什么是pandas? 2. 为什么要学习pandas? 3. pandas的优势 4. 下载安装pandas 二、Pandas的数据类型 1. Series 2. DataFrame

努力让自己发光,对的人才能迎着光而来 17万+

DragVideo 快速上手:3 个步骤让你的视频播放器支持窗口拖拽

在手机上追剧时,你是否也希望像网页端那样,把正在播放的视频缩小后拖到屏幕角落继续观看?**DragVideo** 正是一个开源的 **Android 视频播放器拖拽方案**,它让视频播放器在**播放视频时支持窗口拖拽**、缩放和贴边隐藏,交互体验媲美主流视频网站。本文只用 3 个简单步骤,带你快速上手 DragVideo。 [![DragVideo 视频播放器窗口拖拽效果参考:网页端悬浮小窗播放

gitblog_00827的博客 458

【云原生】Docker可视化工具Portainer使用详解

Docker可视化工具Portainer使用详解

congge 1万+

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Android视频播放器手势控制与动态分辨率切换实现

视频播放器是移动多媒体应用的核心组件,其基础能力涵盖解码、渲染与交互控制。在Android平台,MediaPlayer提供轻量级系统级支持,而ExoPlayer则以模块化架构支撑高阶功能如自适应码率、无级倍速与字幕渲染。理解二者分工与桥接机制,是构建高性能播放体验的前提。手势滑动控制需结合MotionEvent物理建模与阻尼算法,避免误触发;分辨率调节本质是轨道选择与MediaCodec重初始化过程,依赖TrackSelector与自适应带宽计量。这些技术不仅提升用户操作跟手度与视觉一致性,更广泛应用于教育

weixin_28292761的博客 251

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

上一篇: 【Android 技术难点突破】要想实现图文混排时,你会怎么做?来来来,看Colin怎么做
下一篇: 【Android 源码解析】从源码角度深入分析Handler消息机制
Colin_Mindset
博客等级 码龄12年 159粉丝 105原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值