使用声网 SDK 为 Android App 添加视频直播

这篇具有很好参考价值的文章主要介绍了使用声网 SDK 为 Android App 添加视频直播。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用声网 SDK 为 Android App 添加视频直播

视频互动直播是当前比较热门的玩法,我们经常见到有PK 连麦、直播答题、一起 KTV、电商直播、互动大班课、视频相亲等。

本文将演示如何通过声网视频 SDK 在 Android 端实现一个视频直播应用。

🔗点击这里注册声网账号后,开发者每个月可获得 10000 分钟的免费使用额度,可实现各类实时音视频场景。

话不多说,我们开始动手实操。

一些前提条件
  • Agora 开发者帐户(声网开发者注册指南)
  • Android Studio
  • Android 开发基础知识

一、 通过开源Demo,体验视频直播

可能有些人,还不了解我们要实现的功能最后是怎样的。所以我们在 GitHub上提供一个开源的基础视频直播示例项目,在开始开发之前你可以通过该示例项目体验视频直播的体验效果。
Github:GitHub - Meherdeep/agora-android-live-streaming 1

使用声网 SDK 为 Android App 添加视频直播

588×1228 79.9 KB

在这里,我添加了两个直播流,同时可以让多个观众订阅它。

二、 视频直播的技术原理

我们在这里要实现的是视频直播,Agora 的视频直播可以实现互动效果,所以也经常叫互动直播。你可以理解为是多个用户通过加入同一个频道,实现的音视频的互通,而这个频道的数据,会通过声网的 Agora SD-RTN 实时网络来进行低延时传输的。

需要特别说明的是,Agora互动直播不同于视频通话。视频通话不区分主播和观众,所有用户都可以发言并看见彼此;而互动直播的用户分为主播和观众,只有主播可以自由发言,且被其他用户看见。
下图展示在 App 中集成 Agora 互动直播的基本工作流程:
使用声网 SDK 为 Android App 添加视频直播

实现互动直播的步骤如下:

1.设置角色:互动直播频道中,用户角色可以是主播或者观众。主播在频道内发布音视频流,观众仅可订阅音视频流。

2.获取 Token:当 App 客户端加入频道时,你需要通过 Token 验证用户身份。App 客户端向 App 服务器发送请求,并获取 Token,然后在客户端加入频道时验证用户身份。

3.加入频道:调用 joinChannel 创建并加入频道。使用同一频道名称的 App 客户端默认加入同一频道。

4.在频道内发布和订阅音视频:加入频道后,角色为主播的 App 客户端可以发布音视频。对于角色为观众的客户端,如果想要发布音视频,可以调用 setClientRole 切换用户角色。

App 客户端加入频道需要以下信息:

  • 频道名称:用于标识直播频道的字符串。
  • App ID:Agora 随机生成的字符串,用于识别你的 App,可从 Agora 控制台获取,(Agora控制台链接:Dashboard
  • 用户ID:用户的唯一标识。你需要自行设置用户 ID,并确保它在频道内是唯一的。
  • Token:在测试或生产环境中,你的 App 客户端会从你的服务器中获取 Token。为方便快速测试,你也可以获取临时 Token。临时 Token 的有效期为 24 小时。

三、 开发环境

声网Agora SDK 的兼容性良好,对硬件设备和软件系统的要求不高,开发环境和测试环境满足以下条件即可:
• Android SDK API Level >= 16
• Android Studio 2.0 或以上版本
• 支持语音和视频功能的真机
• App 要求 Android 4.1 或以上设备

以下是本文的开发环境和测试环境:

开发环境

• Windows 10 家庭中文版
• Java Version SE 8
• Android Studio 3.2 Canary 4

测试环境

• Samsung Nexus (Android 4.4.2 API 19)
• Mi Note 3 (Android 7.1.1 API 25)

如果你此前还未接触过声网 Agora SDK,那么你还需要做以下准备工作:

• 注册一个声网账号,进入后台创建 AppID、获取 Token,详细方法可参考这篇教程;(这篇教程:404 - 知乎
• 下载声网官方最新的互动直播SDK;(互动直播SDK链接:[下载 - 全部产品 - 文档中心 - 声网Agora](https://docs.agora.io/cn/All/downloads?platform=All Platforms#SDK Downloads))

四、 项目设置

1. 实现互动直播之前,参考如下步骤设置你的项目:

如需创建新项目,在 Android Studio里,依次选择 Phone and Tablet > Empty Activity,创建 Android 项目。(创建 Android 项目链接:https://developer.android.com/studio/projects/create-project)
创建项目后,Android Studio会自动开始同步 gradle。请确保同步成功再进行下一步操作。

2. 集成SDK, 本文推荐使用gradle方式集成Agora SDK:

a. 在 /Gradle Scripts/build.gradle(Project: ) 文件中添加如下代码,以添加 jcenter依赖:
buildscript {
     repositories {
         ...
         jcenter()
     }
     ...
}
 
  allprojects {
     repositories {
         ...
         jcenter()
     }
}
b. 在 /Gradle Scripts/build.gradle(Module: .App) 文件中添加如下代码,将 Agora 视频 SDK 集成到你的 Android 项目中:
...
dependencies {
 ...
 // x.y.z,请填写具体的 SDK 版本号,如:3.5.0。
 // 通过发版说明获取最新版本号。
 implementation 'io.agora.rtc:full-sdk:x.y.z'
//本例使用布局相关设置constraintlayout
implementation  'androidx.constraintlayout:constraintlayout:2.0.4'
}

3. 权限设置

在 /App/Manifests/AndroidManifest.xml 文件中的 `` 后面添加如下网络和设备权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />

4. 导入Agora相关的类

在/app/src/main/java/com/agora/samtan/agorabroadcast/VideoActivity文件中,加入如下代码:

package com.agora.samtan.agorabroadcast;
import io.agora.rtc.Constants;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;

5. 设置Agora账号信息

在/app/src/main/res/values/strings.xml文件中,将你的AppID填写到private_App_id中:

<resources>
    ……
<string name="private_App_id">填写位置</string>
……
</resources>

五、 客户端实现

本节介绍如何使用Agora视频SDK在你的App里实现视频直播的几个小贴士:

1. 检查并获取必要权限

启动应用程序时,检查是否已在App中授予了实现视频直播所需的权限。在onCreate函数中调用如下代码:

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

        int MY_PERMISSIONS_REQUEST_CAMERA = 0;
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, MY_PERMISSIONS_REQUEST_CAMERA);

        }
}

2. 实现互动直播逻辑

打开你的App,创建RtcEngine实例,启用视频后加入频道。如果本地用户是主播,则将本地视频发布到用户界面下方的视图中。如果另一主播加入该频道,你的App会捕捉到这一加入事件,并将远端视频添加到用户界面右上角的视图中。
互动直播的API使用时序见下图:

使用声网 SDK 为 Android App 添加视频直播

image822×1048 106 KB

按照以下步骤实现该逻辑:

a) 初始化RtcEngine
RtcEngine类包含应用程序调用的主要方法,调用RtcEngine的接口最好在同一个线程进行,不建议在不同的线程同时调用。

目前Agora Native SDK只支持一个RtcEngine实例,每个应用程序仅创建一个RtcEngine对象。RtcEngine类的所有接口函数,如无特殊说明,都是异步调用,对接口的调用建议在同一个线程进行。所有返回值为int型的API,如无特殊说明,返回值0为调用成功,返回值小于0为调用失败。

在VideoActivity文件中,通过initializeAgoraEngine用于初始化RtcEngine的方法:

    private void initalizeAgoraEngine() {
        try {
            mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.private_App_id), mRtcEventHandler);
        } catch (Exception e) {
            e.printStackTrace();
        }
}

另外,有个重要的IRtcEngineEventHandler接口类用于SDK向应用程序发送回调事件通知,应用程序通过继承该接口类的方法获取SDK的事件通知。

接口类的所有方法都有缺省(空)实现,应用程序可以根据需要只继承关心的事件。在回调方法中,应用程序不应该做耗时或者调用可能会引起阻塞的API(如SendMessage),否则可能影响SDK的运行。内容如下:

private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler()
{
    /**Reports a warning during SDK runtime.
     * Warning code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_warn_code.html*/
    @Override
    public void onWarning(int warn)
    {
        Log.w(TAG, String.format("onWarning code %d message %s", warn, RtcEngine.getErrorDescription(warn)));
    }
 
    /**Reports an error during SDK runtime.
     * Error code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html*/
    @Override
    public void onError(int err)
    {
        Log.e(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
        showAlert(String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
    }
 
    /**Occurs when a user leaves the channel.
     * @param stats With this callback, the Application retrieves the channel information,
     *              such as the call duration and statistics.*/
    @Override
    public void onLeaveChannel(RtcStats stats)
    {
        super.onLeaveChannel(stats);
        Log.i(TAG, String.format("local user %d leaveChannel!", myUid));
        showLongToast(String.format("local user %d leaveChannel!", myUid));
    }
 
    /**Occurs when the local user joins a specified channel.
     * The channel name assignment is based on channelName specified in the joinChannel method.
     * If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
     * @param channel Channel name
     * @param uid User ID
     * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
    @Override
    public void onJoinChannelSuccess(String channel, int uid, int elapsed)
    {
        Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
        showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
        myUid = uid;
        joined = true;
        handler.post(new Runnable()
        {
            @Override
            public void run()
            {
                join.setEnabled(true);
                join.setText(getString(R.string.leave));
            }
        });
    }
 
    @Override
    public void onRemoteAudioStats(io.agora.rtc.IRtcEngineEventHandler.RemoteAudioStats remoteAudioStats) {
        statisticsInfo.setRemoteAudioStats(remoteAudioStats);
        updateRemoteStats();
    }
 
    @Override
    public void onLocalAudioStats(io.agora.rtc.IRtcEngineEventHandler.LocalAudioStats localAudioStats) {
        statisticsInfo.setLocalAudioStats(localAudioStats);
        updateLocalStats();
    }
 
    @Override
    public void onRemoteVideoStats(io.agora.rtc.IRtcEngineEventHandler.RemoteVideoStats remoteVideoStats) {
        statisticsInfo.setRemoteVideoStats(remoteVideoStats);
        updateRemoteStats();
    }
 
    @Override
    public void onLocalVideoStats(io.agora.rtc.IRtcEngineEventHandler.LocalVideoStats localVideoStats) {
        statisticsInfo.setLocalVideoStats(localVideoStats);
        updateLocalStats();
    }
 
    @Override
    public void onRtcStats(io.agora.rtc.IRtcEngineEventHandler.RtcStats rtcStats) {
        statisticsInfo.setRtcStats(rtcStats);
    }
};

所以,在我们的initialize函数中,我们将mRtcEventHandler作为参数之一传递给了create方法,这设置了一系列回调事件,每当用户加入频道或离开频道时就会触发这些事件。

private IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {

        @Override
        public void onUserJoined(final int uid, int elapsed) {
            super.onUserJoined(uid, elapsed);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    setupRemoteVideo(uid);
                }
            });
        }

        @Override
        public void onUserOffline(int uid, int reason) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    onRemoteUserLeft();
                }
            });
        }
    };

b) 设置频道场景和角色
setChannelProfile()是一个使用我们AgoraRtcEngine对象引用的方法。Agora提供了各种配置文件,可以通过该方法调用并集成到应用中。

setClientRole()方法,将用户的角色设置为主播或观众(默认)。这个方法应该在加入频道之前调用。加入频道后可以再次调用,切换客户端角色。

为了方便体验互动直播中主播角色和观众角色的效果,我们将在我们的MainActivity类中添加两个方法:
• 当用户从单选按钮中选择一个选项时,将调用第一个方法。我们将相应地设置一个变量。我们将其设置为一个值,该值将确定用户是主播还是观众。

public void onRadioButtonClicked(View view) {
        boolean checked = ((RadioButton) view).isChecked();
        switch (view.getId()) {
            case R.id.host:
                if (checked) {
                    channelProfile = Constants.CLIENT_ROLE_BROADCASTER;
                }
                break;
            case R.id.audience:
                if (checked) {
                    channelProfile = Constants.CLIENT_ROLE_AUDIENCE;
                }
                break;
        }
}

• 然后我们实现一个在用户提交详细信息时调用的函数。在这里,我们将获得我们需要的所有详细信息,并将它们发送到下一个activity。

public void onSubmit(View view) {
        EditText channel = (EditText) findViewById(R.id.channel);
        String channelName = channel.getText().toString();
        Intent intent = new Intent(this, VideoActivity.class);
        intent.putExtra(channelMessage, channelName);
        intent.putExtra(profileMessage, channelProfile);
        startActivity(intent);
}

c) 开始视频
setupVideoProfile()函数用于定义视频需要渲染的方式。你可以对帧速率、比特率、方向、镜像模式和降级偏好等属性使用自己的自定义配置。

private void setupVideoProfile() {
        mRtcEngine.enableVideo();

        mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(VideoEncoderConfiguration.VD_640x480, VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
                VideoEncoderConfiguration.STANDARD_BITRATE,
                VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
}

d) 设置本地视频
setupLocalVideo()函数用于从我们的AgoraRtcEngine中引用setupLocalVideo方法,我们通过它为我们的本地用户设置一个在直播流中使用的表面视图:

private void setupLocalVideo() {
        FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container);
        SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());
        surfaceView.setZOrderMediaOverlay(true);
        container.addView(surfaceView);
        mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
    }

e) 加入频道
频道是人们在同一个视频通话中的公共空间。joinChannel()方法可以这样调用:

private void joinChannel() {
        mRtcEngine.joinChannel(token, channelName, "Optional Data", 0);
}

该方法需要四个参数才能成功运行:
• Token:建议对在生产环境中运行的所有RTE APP进行Token身份验证。更多关于声网Agora平台基于令牌的认证信息,请参见https://docs.agora.io/cn/Video/token?platform=All%20Platforms。
• 频道名称:需要一个字符串,让用户进入视频通话。
• 可选信息:这是一个可选字段,你可以通过它传递有关频道的其他信息。
• uid:每个加入频道的用户的唯一ID。如果传入0或null值,Agora会自动为每个用户分配一个uid。

注意:此项目仅供参考和开发环境使用,不适用于生产环境。建议对在生产环境中运行的所有RTE APP进行Token身份验证。

本例中初始化App,调用核心方法来创建并加入Agora直播频道。在VideoActivity文件中,在onCreate函数后添加如下代码:

package com.agora.samtan.agorabroadcast;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;   ;//;.;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.Appcompat.App.AppCompatActivity;
import io.agora.rtc.Constants;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;

public class VideoActivity extends AppCompatActivity {
  private RtcEngine mRtcEngine;
  private String channelName;
  private int channelProfile;
  
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video);

        Intent intent = getIntent();
        channelName = intent.getStringExtra(MainActivity.channelMessage);
        channelProfile = intent.getIntExtra(MainActivity.profileMessage, -1);

        if (channelProfile == -1) {
            Log.e("TAG: ", "No profile");
        }

        initAgoraEngineAndJoinChannel();
    }

}

我们宣布了一个名为initAgoraEngineAndJoinChannel的方法,它将调用直播过程中所需的所有其他方法。我们还定义了事件处理程序,它将决定当远程用户加入或离开或静音时调用哪些方法。

private void initAgoraEngineAndJoinChannel() {
        initalizeAgoraEngine();
        mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
        mRtcEngine.setClientRole(channelProfile);
        setupVideoProfile();
        setupLocalVideo();
        joinChannel();
}

f) 当远端主播加入频道时添加远端界面
在VideoActivity文件中,initializeAndJoinChannel函数后加入如下代码:

    private void setupRemoteVideo(int uid) {
        FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container);
        SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());
        container.addView(surfaceView);
        mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
    }

g) 释放资源
最后,我们添加onDestroy方法来释放我们使用过的资源。相关代码如下:

@Override
    protected void onDestroy() {
        super.onDestroy();

        leaveChannel();
        RtcEngine.destroy();
        mRtcEngine = null;
}

至此,完成,运行看看效果。拿两部手机安装编译好的App,加入同一个频道名,分别选择主播角色和观众角色,如果2个手机都能看见同一个自己,说明你成功了。

如果你在开发过程中遇到问题,可以访问论坛提问与声网工程师交流(链接:https://rtcdeveloper.agora.io/
文章来源地址https://www.toymoban.com/news/detail-410350.html

到了这里,关于使用声网 SDK 为 Android App 添加视频直播的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue+萤石云ezuikit.js播放直播流视频(声音自动关闭,添加控件,多个视频播放)

    vue+萤石云ezuikit.js播放直播流视频(声音自动关闭,添加控件,多个视频播放) 实例使用 引入最新的ezuikit.js 看过官网的文档都知道萤石云的文档是多么的‘善解人意’哈,懂得都懂,很多东西没有说清楚的,只能靠自己去摸索或者看他们的demo来一点点测试实现 官网地址:

    2024年02月04日
    浏览(126)
  • 五大优化技巧,让你的视频直播app源码更加流畅

    视频直播app源码在确保流畅体验方面是至关重要的。为了提升性能,以下是几项关键的优化技巧: 使用轻量级编码器和解码器 :选择高效的编码器和解码器,以减少资源占用,并确保视频流畅播放。 优化视频分辨率和比特率 :根据用户设备和网络条件,适当调整视频分辨率

    2024年02月09日
    浏览(43)
  • 视频直播新时代,低延时直播交互,Web,Android,WebRtc推流拉流测试

    直播现在已经深入了生活,学习,工作和娱乐方方面面,由于前些年的技术所限,传统rtmp,flv,m3u8 技术让直播快速启动项目产品,但也有很多不足,特别的交互式直播,一直是其中的痛点,延时较大(1-10秒不等) 让用户即想用,已不是太爽,随技术进步,Webrtc已经切入了视

    2024年01月19日
    浏览(56)
  • LiveNVR监控流媒体Onvif/RTSP功能-海康大华天地伟业SDK接入拉转直播流文件ISUP接入转换成视频直播流地址输出RTSP/WebRTC/RTMP/HLS/HTTP-FLV/WS-FLV

    某些场景下(小区监控、厂区监控等),我们需要接入摄像头,但手头上只能获取到摄像头直播流地址,如RTSP流地址。我们可能需要将视频流发布web页面,可以用浏览器快速无插件的浏览观看。我们可能需要云台操作摄像头。我们可能需要将视频监控的视频流做集中的存储录

    2024年02月03日
    浏览(83)
  • Android-音视频学习系列-(八)基于-Nginx-搭建(rtmp、http)直播服务器

    #!/bin/sh HTTP_FLV_MODULE_PATH=…/nginx-http-flv-module-1.2.7 OpenSSL_PATH=…/openssl-1.1.1d #–prefix=./bin 代表编译完成之后输出的路径地址 #–add-module 将拓展模块添加到当前一起编译 ./configure --prefix=./bin –add-module= H T T P F L V M O D U L E P A T H   − − w i t h − o p e n s s l = HTTP_FLV_MODULE_PATH --with

    2024年04月15日
    浏览(61)
  • 使用微信小程序播放视频直播

    观众端使用live-player进行直播视频的播放,live-player组件从视频云拉流,并用于实时音视频播放。live-player支持两种模式:Live和RTC,前者用于直播播放,后者用于实时音视频通话。在使用live-player组件实现直播播放前先看看live-player组件的属性以及方法。 属性定义 Src 用于音视

    2023年04月15日
    浏览(62)
  • 直播相关——声网rtc SDK

    遥想约4年前,也自行调研过,虽然最终没有在实际项目中落地。 声网Android端集成与一对一音视频功能实现 现在,终于要开始在项目中正式落地了,而声网也从原来的v3.x升级到了v4.x版本了。根据官网介绍,两大版本间改动还是比较大的。本次集成落地,会直接用v4.x版本。

    2024年03月12日
    浏览(37)
  • 使用js搭建简易的WebRTC实现视频直播

    首先需要一个信令服务器,我们使用nodejs来搭建。两个端:发送端和接收端。 我的目录结构如下图: 流程 创建一个文件夹 WebRTC-Test。 进入文件夹中,新建一个node的文件夹。 使用终端并进入node的目录下,使用 创建package.json。 新建server.js,复制一下代码 下载信令服务器的依

    2024年02月09日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包