EXOPlayer简要学习及应用

exoplayer 播放rtmp流 阅读详情

本想着上效果图的,可惜图片太大了不允许上传参观移步GitHub

EXOPlayer是Google官方开源的一种播放器官方介绍 ,能够支持DASH, SmoothStreaming 和 HLS,可惜不能支持Adobe的rtsp、rtmp(有时间我会把B站开源播放器放上来IjkPlayer,那才叫功能强大且易上手这是后话。毕竟EXOPlayer是Google的亲儿子,我也是先应用的它而后转去ijkPlayer的)。至于以上内容如有内容不明白的可以自行查询不是我这里的重点,都是些视频相关概念,我还是着重应用。

我项目应用的EXOPlayer版本为2.0.0,此时此刻最新的版本为2.5.1。不是我不想用最新的,而是在我刚开始接触看2.5.1源码的时候,项目中分了多个Module且功能都比较独立,应用的时候必须引用多个模块,想短时间利用来开发难度很大,需要了解的内部逻辑很多,demo估计还没看完就懵了。
这里写图片描述
相反2.0.0版本只需要

compile 'com.google.android.exoplayer:exoplayer:r2.0.0'

我所抽取的类就两个PlayerActivity(播放界面)和EventLogger(日志打印),应用的时候一个Intent过去就解决了。

以下是干货
Intent:

    private void goActivity(String url) {
        Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
        intent.setData(Uri.parse(url)); //传入视频地址  在PlayerActivity内会根据后缀的不同区分不同的资源
        intent.setAction(PlayerActivity.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //ShareElement 效果
            startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, imageView, "shareImage").toBundle());
        } else {
            startActivity(intent);
        }
    }

PlayerActivity的控件SimpleExoPlayerView:SimpleExoPlayerView extends FrameLayout可以理解为播放控件,里面包含了播放控制控件PlaybackControlView。

  public SimpleExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    ...
    //控件初始化逻辑
    ...

    //这里才是关键 其实我们看到的视频界面 无非是TextureView 或SurfaceView 这里以下有逻辑解释
    View view = useTextureView ? new TextureView(context) : new SurfaceView(context);
    ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT);
    view.setLayoutParams(params);
    surfaceView = view;
    layout.addView(surfaceView, 0);
  }

控件初始化完成,需要输入player

  public void setPlayer(SimpleExoPlayer player) {
    ...
    //重置操作
    ...

    this.player = player;

    if (player != null) {//我们看到的就是这两个控件
      if (surfaceView instanceof TextureView) {
        player.setVideoTextureView((TextureView) surfaceView);
      } else if (surfaceView instanceof SurfaceView) {
        player.setVideoSurfaceView((SurfaceView) surfaceView);
      }
      player.setVideoListener(componentListener);
      player.addListener(componentListener);
      player.setTextOutput(componentListener);
    }
    setUseController(useController);
  }

  public void setUseController(boolean useController) {
    this.useController = useController;
    //controller 是控制控件 播放暂停之类
    if (useController) {
    //这种设计方式比较好 不是给player设置各种控件 而是让控件去监听变化  松耦合思想
      controller.setPlayer(player);
    } else {
      controller.hide();
      controller.setPlayer(null);
    }
  }

PlayerActivity的基本思路:
onCreate 初始化控件;
onNewIntent 释放原有player设置新数据
onStart onResume 初始化initializePlayer()
onPause onStop 释放releasePlayer()
关键就是以上几个方法,源码中冗长的代码是一些回调和抽离方法

    private void initializePlayer() {
        Intent intent = getIntent();
        if (player == null) {
            //数字证书相关  如果需求不需要的话可以移除
            boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
            UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
                    ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
            DrmSessionManager drmSessionManager = null;
            if (drmSchemeUuid != null) {
                String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
                String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
                Map<String, String> keyRequestProperties;
                if (keyRequestPropertiesArray == null || keyRequestPropertiesArray.length < 2) {
                    keyRequestProperties = null;
                } else {
                    keyRequestProperties = new HashMap<>();
                    for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
                        keyRequestProperties.put(keyRequestPropertiesArray[i],
                                keyRequestPropertiesArray[i + 1]);
                    }
                }
                try {
                    drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl,
                            keyRequestProperties);
                } catch (UnsupportedDrmException e) {
                    int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
                            : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
                            ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
                    showToast(errorStringId);
                    return;
                }
            }
            //数字证书相关

            eventLogger = new EventLogger();//日志打印
            TrackSelection.Factory videoTrackSelectionFactory =
                    new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER);
            trackSelector = new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory);
            trackSelector.addListener(this);
            trackSelector.addListener(eventLogger);
            player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
                    drmSessionManager, preferExtensionDecoders);
            player.addListener(this);
            player.addListener(eventLogger);
            player.setAudioDebugListener(eventLogger);
            player.setVideoDebugListener(eventLogger);
            player.setId3Output(eventLogger);
            simpleExoPlayerView.setPlayer(player);
            if (shouldRestorePosition) {
                if (playerPosition == C.TIME_UNSET) {
                    player.seekToDefaultPosition(playerWindow);
                } else {
                    player.seekTo(playerWindow, playerPosition);
                }
            }
            player.setPlayWhenReady(shouldAutoPlay);
            playerNeedsSource = true;
        }
        if (playerNeedsSource) {
            String action = intent.getAction();
            Uri[] uris;
            String[] extensions;
            if (ACTION_VIEW.equals(action)) {// 多个资源的话会无缝连接播放
                uris = new Uri[]{intent.getData()};
                extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)};
            } else if (ACTION_VIEW_LIST.equals(action)) {
                String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
                uris = new Uri[uriStrings.length];
                for (int i = 0; i < uriStrings.length; i++) {
                    uris[i] = Uri.parse(uriStrings[i]);
                }
                extensions = intent.getStringArrayExtra(EXTENSION_LIST_EXTRA);
                if (extensions == null) {
                    extensions = new String[uriStrings.length];
                }
            } else {
                showToast(getString(R.string.unexpected_intent_action, action));
                return;
            }
            if (Util.maybeRequestReadExternalStoragePermission(this, uris)) {// 外部存储权限
                // The player will be reinitialized if the permission is granted.
                return;
            }
            MediaSource[] mediaSources = new MediaSource[uris.length];  //将URL组装成资源类
            for (int i = 0; i < uris.length; i++) {
                mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
            }
            MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
                    : new ConcatenatingMediaSource(mediaSources);
            player.prepare(mediaSource, !shouldRestorePosition);
            playerNeedsSource = false;
        }
    }

    //根据后缀区分资源类型
    private MediaSource buildMediaSource(Uri uri, String overrideExtension) { 
        int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
                : uri.getLastPathSegment());
        switch (type) {
            case Util.TYPE_SS:
                return new SsMediaSource(uri, buildDataSourceFactory(false),
                        new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
            case Util.TYPE_DASH:
                return new DashMediaSource(uri, buildDataSourceFactory(false),
                        new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
            case Util.TYPE_HLS:
                return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
            case Util.TYPE_OTHER:
                return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
                        mainHandler, eventLogger);
            default: {
                throw new IllegalStateException("Unsupported type: " + type);
            }
        }
    }

    private void releasePlayer() {
        if (player != null) {
            shouldAutoPlay = player.getPlayWhenReady();
            shouldRestorePosition = false;
            Timeline timeline = player.getCurrentTimeline();
            if (timeline != null) {
                playerWindow = player.getCurrentWindowIndex();
                Timeline.Window window = timeline.getWindow(playerWindow, new Timeline.Window());
                if (!window.isDynamic) {
                    shouldRestorePosition = true;
                    playerPosition = window.isSeekable ? player.getCurrentPosition() : C.TIME_UNSET; //保存播放位置
                }
            }
            player.release();
            player = null;
            trackSelector = null;
            eventLogger = null;
        }
    }

简要介绍只能做到这种地步了,如果要自己应用或者学习的话多动手多改源码才是正道,不要妄想看一两篇文章就想搞懂。

ExoPlayer集成RTMP扩展实现低延迟直播流播放 本文详细介绍了在Android应用中集成ExoPlayer RTMP扩展以实现低延迟直播流播放的完整方案。通过添加官方extension-rtmp依赖,构建支持RTMP协议的数据源,开发者可以轻松解决Android平台原生播放器对RTMP支持不佳的问题。文章还深入探讨了缓冲策略调优、低延迟模式启用等关键性能优化技巧,并提供了处理常见兼容性问题与实现自动重连等生产级功能的实战指南,帮助开发者构建稳定、低延迟的直播播放体验。 阅读详情

相关推荐

基于ExoPlayer的音频播放器以及进度悬浮窗

简述 最近在使用EXOplayer做关于音频的开发,目标如下 通过service绑定activity,能在后台播放,同时,在退出activity之后,显示一个悬浮窗,悬浮窗能同步播放器的进度,点击则进入播放界面。 关于ExoPlayer 这个库是goole官方推出的,十分强大,根据项目需要我主要使用它来进行音频播放。 列举几篇有参考意义的参考文献 官方介绍 先来一张图片镇楼 使用步骤 ...

linghaoDo的博客 4033

exoplayer播放rtmp流

exoplayer一直在更新,已经和以前的版本不太一样了,而且我这边需要播放rtmp流所以就又重新搞了下exoplayer 如果只是引用的话就直接 implementation 'com.google.android.exoplayer:exoplayer:2.X.X' 或者根据自己的需求引用需要的部分 implementation 'com.google.android.exopla...

红色与青色 7626

在线学习应用

Toast.makeText(CourseListActivity.this, "加载失败", Toast.LENGTH_SHORT).show();// 播放完成保存记录。│ └── RetrofitClient.java // Retrofit实例化。│ └── StudyRecordDao.java // 学习记录操作接口。│ ├── MainActivity.java // 主页面(底部导航)│ └── StudyRecord.java // 学习记录实体。

寒寒豆豆软件公司的博客 92

ExoPlayer RTMP支持H265扩展

ExoPlayer RTMP支持H265扩展 RTMP是由Adobe公司提出的一种应用层的协议。RTMP和FLV格式友好的兼容性,主要体现在RTMP封装可播放的音视频流时,RTMP Packet中封装的音视频数据流时,其实和FLV/tag封装音频和视频数据的方式是相同的,FLV封装采用H264和AAC的方式,以下是对ExoPlayer FLV封装代码扩展H265是编码的。 ExoPlayer/li...

u011921603的博客 3805

Android平台移动学习应用开发实战:以GPlayer为视频播放核心

移动学习平台是将移动计算技术和教学内容相结合的产物。它允许用户随时随地通过移动设备获取教育资源,包括视频讲座、互动课程、在线测试和即时反馈等。这种学习方式打破了传统教育的时空限制,增加了学习的灵活性和个性化。视频播放技术从最初的VHS录像带到DVD,再到流媒体播放,技术的演进不断地提高了用户的观看体验。在移动互联网时代,视频播放技术需要考虑网络条件、设备性能和用户交互性等因素。例如,流媒体技术允许用户无需下载整个视频文件即可观看,这样既节省了带宽也提高了观看的即时性。

weixin_35762258的博客 970

Android在线学习系统开发:MVVM架构与ExoPlayer实战指南

移动应用开发是当前计算机教育与实践的热门领域,其核心在于如何构建稳定、高效且用户体验良好的客户端。MVVM(Model-View-ViewModel)架构模式通过数据驱动的方式,实现了视图与业务逻辑的清晰分离,配合Kotlin协程等现代异步编程技术,能有效提升代码的可维护性和健壮性。这种架构模式在需要处理复杂数据流和状态管理的应用中价值显著,例如在线教育、内容消费等场景。本文聚焦于Android平台,深入探讨如何运用MVVM架构、Room数据库以及ExoPlayer等关键技术,构建一个功能完整的移动终端在线

weixin_32745665的博客 253

Android ExoPlayer的学习与应用

ExoPlayer的学习与应用 ExoPlayer是Google自家推出的一个适用于Android的可扩展媒体播放器的库,该库功能非常强大,且Api实现简单,做到了几行代码就能做到加载,缓存等一系列操作,按照谷歌官方的说法,该库有以下几点优势和劣势: ExoPlayer与MediaPlayer内置的Android相比具有许多优势: 支持HTTP动态自适应流媒体(DASH)和Smoo...

求知的程序员 3251

ke-rtsp:一种基于Media Source Extensions(简称MSE)技术实现的html5实时视频直播方案。Node.js调用ffmpeg包装rtsp流,然后通过socket.io转发包装后的流,前端html5获取流数据并通过MSE把流一点一点喂给video标签进行视频播放。能够支持谷歌浏览器、火狐浏览器、Edge浏览器、安卓原生浏览器,不支持IE。高性能,支持同时转发多路视频,占用硬件资源不多。低延时,延时1s

ke-rtsp 一种基于Media Source Extensions(简称MSE)技术实现的html5实时视频直播方案。Node.js调用ffmpeg包装rtsp流,然后通过socket.io转发包装后的流,前端html5获取流数据并通过MSE把流一点一点喂给video标签进行视频播放。能够支持谷歌浏览器、火狐浏览器、Edge浏览器、安卓原生浏览器,不支持IE。高性能,支持同时转发多路视频,占用硬件资源不多。低延时,延时1s。 运行示例项目 1、克隆或者下载项目到本地。 2、命令行进入到项目目录,执行命令node sample/index.js。 3、修改sample/index.html中的config对象的url参数为你的rtsp测试地址。 4、浏览器打开http://localhost:8080/。 集成到你的项目中 代码量较少,请自行加工后将源代码添加到你的项目中,npm上的代码

codelab-exoplayer-intro:轻松掌握ExoPlayer媒体播放

codelab-exoplayer-intro:轻松掌握ExoPlayer媒体播放 项目介绍 在现代移动应用开发中,流畅的媒体流播放是用户体验的重要组成部分。codelab-exoplayer-intro 是一个开源项目,旨在通过一系列互动教程帮助开发者快速上手并掌握 Google 的 ExoPlayer。该项目包含了一系列代码示例和详细的指导,帮助开发者学习如何使用 ExoPlayer 来实现高...

gitblog_00279的博客 537

别再到处找模型了!手把手教你用Xinference+Docker部署本地私有大模型(Llama2/Qwen实战)

本文详细介绍了如何使用Xinference和Docker部署本地私有化大模型(如Llama2/Qwen),涵盖硬件配置、模型管理、Docker部署及性能优化等关键步骤。通过实战案例,帮助开发者快速构建企业级AI推理服务,确保数据安全与高效运行。

weixin_28683065的博客 232

LightGBM处理类别特征和缺失值有多省心?一个真实业务数据案例带你上手

本文通过真实电商数据案例,详细解析了LightGBM如何高效处理类别特征和缺失值。相比传统方法,LightGBM仅需一行代码即可自动处理高基数类别特征和缺失值,大幅提升模型训练效率和预测准确度,特别适合业务场景中的脏数据处理。

weixin_30689307的博客 460

【Exoplayer】简单使用Exoplayer

简单使用Exoplayer

lily_i的博客 2128

SAE J1850协议:从波形解析到汽车诊断实战

本文深入解析SAE J1850协议在汽车诊断中的应用,重点对比其PWM和VPW两种实现方式的波形特征、帧结构及诊断要点。通过实测案例,详细介绍了如何通过波形分析精准定位通用和福特等美系车型的通信故障,提升汽车电子诊断实战能力。

hill8的博客 966

推荐开源项目:ExoPlayer2 示例工程

推荐开源项目:ExoPlayer2 示例工程 在Android开发中,寻找一款可靠且易用的媒体播放库至关重要。现在,我们向您推荐一个针对初学者设计的ExoPlayer2示例项目,它将引导您轻松入门ExoPlayer的使用。 1、项目介绍 ExoPlayer2 Sample Project 是由开发者Yusuf Çakmak创建的一个开源项目,其目标是帮助开发者了解如何使用ExoPlayer播放音频...

gitblog_00042的博客 583

数字IC设计随笔之二(VCS、DVE|Verdi单步调试)

VCS+Verdi单步调试

ic疾风传 1万+

基于传感器数据实现火灾报警预测分析

火警 系统当检测到烟雾、火灾、一氧化碳或其他与火灾有关或一般通知的紧急情况时,向人们发出警告

DT程序员的博客 723

推荐项目:VideoPlayerInsideRecyclerView - 打造流畅的瀑布流视频播放体验

推荐项目:VideoPlayerInsideRecyclerView - 打造流畅的瀑布流视频播放体验 在移动开发领域,将高质量视频内容融入列表滚动浏览的场景已经变得极为常见,特别是在社交媒体应用和在线教育平台中。然而,实现这一功能时常伴随着性能挑战和复杂的代码管理。今天,我们要向您推荐一个精巧的解决方案——VideoPlayerInsideRecyclerView。该项目通过优雅地整合ExoPl...

gitblog_00234的博客 728

【软考高项】【计算专题】- 3 - 进度类 - 双代号网络图

共需压缩 20 天。在双代号网络图中,工作一般使用箭线表示,任意一条箭线都需要占用时间、消耗资源,工作名称写在箭线的上方,而消耗的时间则写在箭线的下方或者工作后面。反映的是前后工作的交接点,节点中的编号可以任意编写,但应保证后续工作的节点比前面节点的编号大,即上图图中的1⃣️4⃣️ 即表示 B 活动和 D 活动间有依赖关系,D 活动的开始,依赖 B 活动的结束。B活动完成了一半, 各计划的实际支出为A活动6500元, B活动1000元, C活动280元, D。

欢迎来到我的博客,一起探索代码里的世界! 4563

Android音频开发(三):使用ExoPlayer播放音频

一、音频播放的权限及依赖: 音频播放可能涉及到的权限: <uses-permission android:name="android.permission.INTERNET" /> <!--音频模式切换权限--> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <!--唤屏/息屏权限--> <uses-p.

红日 8911

把雷达强度特征融合于 Cartographer的实现流程 :从地图构建到在线定位

摘要:本文提出在激光SLAM中融合强度特征的方法,解决几何匹配在退化环境中的失效问题。详细介绍了两种高反地图生成方式:离线使用assets_writer工具处理优化轨迹,在线实时累积高反点;以及权重网格的构建与预计算金字塔方法。定位阶段通过加权得分公式融合几何和强度信息,采用自适应缩放策略平衡特征依赖。该方案在不显著增加计算负担的前提下,有效提升了对称环境等退化场景的定位稳定性,对利用人工路标的移动机器人具有实用价值。

Studying…… 1328

第03章 Hermes Agent 核心原理

环境跑通之后,读者会很自然地想问:Hermes 的内部到底是怎么转起来的?为什么它能做到"越用越聪明"?长对话不会忘记上下文吗?这一章用尽量短的篇幅把核心原理讲清楚。理解原理不是为了纸上谈兵,而是为了在第7章实战时能判断"为什么 Hermes 这么响应"以及"我应该把哪类任务交给它"。

亢少军的博客 142

社区说|多才多艺: 探索 Android 应用更多可能

Android 应用一直以开放和功能性著称,通过丰富的系统接口和诸多社区解决方案,任何美妙的金点子都可以实现。尝试跨不同应用执行并发 IPC 时,Android 接口定义语言 (AIDL) 可以帮到你。发现原生 Android MediaPlayer API 已经无法满足你对细节的需求时,包括 DASH 和 SmoothStreaming 自适应播放,ExoPlayer 将是不错的选择。甚至在设备...

谷歌开发者 132

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

用ExoPlayer实现简单的影视播放

Toast.makeText(getBaseContext(), "自动为你播放下一集", Toast.LENGTH_SHORT).show();Toast.makeText(getBaseContext(), "已经是最后一集了", Toast.LENGTH_SHORT).show();Toast.makeText(getBaseContext(), "已经是最后一集了", Toast.LENGTH_SHORT).show();// 更新选中的视频位置到下一个。// 更新选中的视频位置到下一个。

qq_59433474的博客 1157

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【今晚开播】社区说|多才多艺: 探索 Android 应用更多可能

Android 应用一直以开放和功能性著称,通过丰富的系统接口和诸多社区解决方案,任何美妙的金点子都可以实现。尝试跨不同应用执行并发 IPC 时,Android 接口定义语言 (AIDL) 可以帮到你。发现原生 Android MediaPlayer API 已经无法满足你对细节的需求时,包括 DASH 和 SmoothStreaming 自适应播放,ExoPlayer 将是不错的选择。甚至在设备...

谷歌开发者 114

超分辨率革命:ExoPlayer视频画质提升实战指南

你还在为低分辨率视频在高清屏幕上模糊不清而烦恼吗?还在纠结第三方工具复杂的转码流程?本文将带你通过ExoPlayer的视频处理能力,零代码实现视频超分辨率提升,让普通视频秒变高清画质。读完你将掌握: - ExoPlayer超分辨率核心原理与架构 - 3步实现视频分辨率提升的完整流程 - 实战案例:从480p到1080p的画质增强技巧 - 性能优化与常见问题解决方案 ## ExoPlayer视频处...

gitblog_00082的博客 772

告别变声烦恼:ExoPlayer变速播放如何保持完美音调?

ExoPlayer是一款功能强大的开源媒体播放器,它不仅支持多种媒体格式,还提供了丰富的播放控制功能,其中变速播放且保持音调稳定是其备受赞誉的特性之一。无论是学习外语、观看教学视频还是欣赏音乐,用户都可能需要调整播放速度,而ExoPlayer的变速播放功能能够在改变速度的同时,确保音频音调不变,带来更优质的听觉体验。 ## 为什么变速播放会导致变声? 通常情况下,当我们调整媒体播放速度时,音频

gitblog_00530的博客 882
上一篇: 根据手势顶部View自动展示与隐藏
下一篇: Kotlin学习中触碰到的知识点
s1991721
博客等级 码龄15年 4粉丝 62原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值