用ExoPlayer实现简单的影视播放

最近简单学了下ExoPlayer,做了一个简单的影视播放demo。

一般的视频资源,网上有一些免费的测试接口,想要的话可以找一下。

实现结果如下:

 

我这里是实现了简单的全屏播放、倍速播放、左右屏幕拖动进度时间、上一集和下一集。

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_weight="3">

    <FrameLayout
        android:id="@+id/player_room"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@color/black">

<!--       视频-->
        <com.google.android.exoplayer2.ui.PlayerView
            android:id="@+id/player_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:controller_layout_id="@layout/exoplayer_mview"/>

<!--        拖拉进度时间-->
        <TextView
            android:id="@+id/slow_time"
            android:textColor="@color/pink"
            android:textSize="20dp"
            android:visibility="gone"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/loading_tip"
            android:text="加载中..."
            android:textColor="@color/white"
            android:visibility="gone"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/double_speed_tip"
            android:text="倍速播放中"
            android:textColor="@color/white"
            android:visibility="gone"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </FrameLayout>

    <LinearLayout
        android:id="@+id/video_introduce"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:layout_weight="2"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:layout_marginBottom="5dp"
            android:text="简介"
            android:textSize="20dp"
            android:textColor="@color/black"/>

        <TextView
            android:id="@+id/item_title"
            android:layout_marginLeft="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp">

            <TextView
                android:id="@+id/item_releaseTime"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="/"/>

            <TextView
                android:id="@+id/item_region"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>


        </LinearLayout>


        <TextView
            android:id="@+id/item_introduce"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:text="剧集"
                android:textSize="20dp"
                android:textColor="@color/black"/>

            <TextView
                android:id="@+id/playList_sum"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </LinearLayout>

<!--        剧集-->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/video_episodes"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"/>

    </LinearLayout>


</LinearLayout>

playerView我是自定义了UI: app:controller_layout_id="@layout/exoplayer_mview"

 exoplayer_mview布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp">

            <TextView
                android:id="@+id/exo_position"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:text="00:00"
                android:textColor="@android:color/white" />

            <com.google.android.exoplayer2.ui.DefaultTimeBar
                android:id="@id/exo_progress"
                android:layout_width="0dp"
                android:layout_height="20dp"
                android:layout_weight="1" />

            <TextView
                android:id="@+id/exo_duration"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="10dp"
                android:layout_marginStart="10dp"
                android:text="00:00"
                android:textColor="@android:color/white" />

        </LinearLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp">

            <ImageView
                android:id="@+id/exo_m_prev"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginLeft="100dp"
                android:layout_alignParentLeft="true"
                android:src="@drawable/pre_btn" />


            <ImageView
                android:id="@+id/exo_play"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/play_btn" />

            <ImageView
                android:id="@+id/exo_pause"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/pause_btn" />

            <ImageView
                android:id="@+id/exo_m_next"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_marginRight="100dp"
                android:layout_alignParentRight="true"
                android:src="@drawable/next_btn" />

            <ImageView
                android:id="@+id/exo_full"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginLeft="30dp"
                android:layout_alignParentRight="true"
                android:src="@drawable/full" />

        </RelativeLayout>


    </LinearLayout>

</FrameLayout>

 activity:

package com.example.classcard;

import static androidx.constraintlayout.helper.widget.MotionEffect.TAG;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.view.GestureDetectorCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.example.classcard.api.Mapi;
import com.example.classcard.api.Result_play;
import com.example.classcard.fragment.MovieFragment;
import com.example.classcard.pojo.Play;
import com.example.classcard.pojo.PlayItem;
import com.example.classcard.pojo.ReVideo;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.DefaultTimeBar;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class PlayVideoActivity extends AppCompatActivity {
    private TextView textView_title;
    private TextView textView_region;
    private TextView textView_releaseTime;
    private TextView textView_introduce;
    private TextView textView_playListSum;
    private PlayerView playerView;
    private RecyclerView recyclerView;
    private String videoId;
    private List<PlayItem> playList = new ArrayList<>();
    private LinearLayout videoIntroduce;
    private SimpleExoPlayer player;
    private boolean isFullscreen = false; // 记录当前是否为全屏状态
    private long playbackPosition = 0;
    private int selectedPosition = 0;
    private String videoTitle;
    private int flag = 0;
    private TextView textView;
    private ReVideo reVideo;
    private EpisodeAdapter adapter;
    private PowerManager.WakeLock wakeLock;
    private TextView doubleSpeedTip;
    private TextView loadingTip;
    private TextView slowTimeTip;
    private GestureDetectorCompat gestureDetector;
    private Boolean isPlay = false;
    private boolean isSpeeding = false;//记录是否在加速中
    private long slowPosition;
    private int slow_flag = 0;//记录是否在滑动屏幕


    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.play_video);

        textView_title = findViewById(R.id.item_title);
        textView_region = findViewById(R.id.item_region);
        textView_releaseTime = findViewById(R.id.item_releaseTime);
        textView_introduce = findViewById(R.id.item_introduce);
        textView_playListSum = findViewById(R.id.playList_sum);
        playerView = findViewById(R.id.player_view);
        videoIntroduce = findViewById(R.id.video_introduce);
        recyclerView = findViewById(R.id.video_episodes);
        doubleSpeedTip = findViewById(R.id.double_speed_tip);
        loadingTip = findViewById(R.id.loading_tip);
        slowTimeTip = findViewById(R.id.slow_time);

        reVideo = (ReVideo) getIntent().getSerializableExtra("videoItem");

        textView_title.setText(reVideo.getTitle());
        textView_region.setText(reVideo.getRegion());
        textView_releaseTime.setText(reVideo.getReleaseTime());
        textView_introduce.setText(reVideo.getDescs());

        videoId = reVideo.getVideoId();

        if (savedInstanceState != null) {
            isFullscreen = savedInstanceState.getBoolean("isFullscreen");
            playbackPosition = savedInstanceState.getLong("playbackPosition");
            selectedPosition = savedInstanceState.getInt("selectedPosition");
        }

        try {
            getData(videoId);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //视频播放
    private void playVideo(String url){
        player = new SimpleExoPlayer.Builder(getBaseContext()).build();
        Uri uri = Uri.parse(url);
        DataSource.Factory dataSourceFactory = new DefaultHttpDataSourceFactory();
        MediaSource mediaSource = new HlsMediaSource.Factory(dataSourceFactory)
                .createMediaSource(uri);

        playerView.setPlayer(player);
        player.prepare(mediaSource);
        player.setPlayWhenReady(true);

        player.seekTo(playbackPosition);

        // 找到控制组件
        PlayerControlView playerControlView = playerView.findViewById(com.google.android.exoplayer2.ui.R.id.exo_controller);
        // 创建 TextView 组件
        if(flag==0){
            textView = new TextView(this);
            videoTitle = reVideo.getTitle();
            textView.setText(videoTitle+playList.get(selectedPosition).getTitle());
            textView.setTextColor(ContextCompat.getColor(this, R.color.white));

            // 设置 TextView 的位置和大小
            FrameLayout.LayoutParams params2 = new FrameLayout.LayoutParams(
                    FrameLayout.LayoutParams.WRAP_CONTENT,
                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics())
            );

            params2.gravity = Gravity.TOP | Gravity.LEFT;
            params2.setMargins(20,20,0,0);
            textView.setLayoutParams(params2);

            playerControlView.addView(textView,params2);

            flag =1;
        }

        //设置全屏按钮
        ImageView fullscreenButton = playerView.findViewById(R.id.exo_full);
        fullscreenButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                toggleFullscreen();
            }
        });

        //上一集和下一集按钮
        ImageView nextButton = playerView.findViewById(R.id.exo_m_next);
        ImageView prevButton = playerView.findViewById(R.id.exo_m_prev);

        nextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (selectedPosition < playList.size() - 1) {
                    selectedPosition++; // 更新选中的视频位置到下一个
                    releasePlayer();
                    playVideo(playList.get(selectedPosition).getChapterPath());
                    textView.setText(videoTitle+playList.get(selectedPosition).getTitle());

                    adapter.notifyDataSetChanged();
                } else {
                    Toast.makeText(getBaseContext(), "已经是最后一集了", Toast.LENGTH_SHORT).show();
                }
            }
        });

        prevButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (selectedPosition > 0) {
                    selectedPosition--; // 更新选中的视频位置到上一个
                    releasePlayer();
                    playVideo(playList.get(selectedPosition).getChapterPath());
                    textView.setText(videoTitle+playList.get(selectedPosition).getTitle());

                    adapter.notifyDataSetChanged();
                } else {
                    Toast.makeText(getBaseContext(), "已经是第一集了", Toast.LENGTH_SHORT).show();
                }

            }
        });

        // 设置player的监听器
        player.addListener(new Player.EventListener() {
            @Override
            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
                if (playbackState == Player.STATE_READY && playWhenReady) {
                    // 视频播放中
                    loadingTip.setVisibility(View.GONE);
                    isPlay = true;
                    // 禁用手机自动锁屏
                    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
                    wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "MyApp:MyWakeLockTag");
                    wakeLock.acquire();

                } else if (playbackState == Player.STATE_READY) {
                    // 视频暂停中
                    isPlay = false;
                    loadingTip.setVisibility(View.GONE);
                    if (wakeLock != null && wakeLock.isHeld()) {
                        wakeLock.release();
                    }
                } else if (playbackState == Player.STATE_ENDED) {
                    // 视频播放完成
                    if(selectedPosition<playList.size()-1){
                        Toast.makeText(getBaseContext(), "自动为你播放下一集", Toast.LENGTH_SHORT).show();
                        selectedPosition++; // 更新选中的视频位置到下一个
                        releasePlayer();
                        playVideo(playList.get(selectedPosition).getChapterPath());
                        textView.setText(videoTitle+playList.get(selectedPosition).getTitle());

                        adapter.notifyDataSetChanged();
                    }else{
                        Toast.makeText(getBaseContext(), "已经是最后一集了", Toast.LENGTH_SHORT).show();
                    }
                } else if (playbackState == Player.STATE_BUFFERING) {
                    // 视频缓冲中
                    isPlay = false;
                    doubleSpeedTip.setVisibility(View.GONE);
                    loadingTip.setVisibility(View.VISIBLE);

                } else if (playbackState == Player.STATE_IDLE) {
                    // 视频空闲状态
                    // ...
                }
            }

        });


        //设置长按倍速播放
        playerView.setOnTouchListener(new View.OnTouchListener() {

            private Handler handler = new Handler();
            private Runnable showTextRunnable = new Runnable() {
                @Override
                public void run() {
                    if (isSpeeding) {
                        player.setPlaybackParameters(new PlaybackParameters(2.0f));
                        doubleSpeedTip.setVisibility(View.VISIBLE);
                    }
                }
            };

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        // 按下时调整播放速度为2倍
                        if(isPlay){
                            isSpeeding = true;
                            handler.postDelayed(showTextRunnable, 800);
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        slowTimeTip.setVisibility(View.GONE);
                        if(slow_flag == 1){
                            player.seekTo(slowPosition);
                            slow_flag = 0;
                        }

                    case MotionEvent.ACTION_CANCEL:
                        // 松开手时恢复正常播放速度
                        isSpeeding = false;
                        player.setPlaybackParameters(new PlaybackParameters(1.0f));
                        doubleSpeedTip.setVisibility(View.GONE);
                        handler.removeCallbacks(showTextRunnable);
                        break;
                }

                return gestureDetector.onTouchEvent(event);
            }
        });


        //拖动进度
        DefaultTimeBar timeBar = playerView.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);

        gestureDetector = new GestureDetectorCompat(this, new GestureDetector.SimpleOnGestureListener() {
            private long duration;
            private static final float SLOW_FACTOR = 0.01f; // 缓慢播放速度的因子
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                // 获取视频的总时长
                if(slow_flag==0){
                    duration = player.getDuration();
                    slowPosition = player.getCurrentPosition();
                    slow_flag = 1;
                }
                // 如果总时长大于0且手势滑动距离不为0
                if (duration > 0) {
                    isSpeeding = false;
                    doubleSpeedTip.setVisibility(View.GONE);

                    if(distanceX>0){
                        slowPosition = Math.max(0, slowPosition - 1000);
                    }else if(distanceX<0){
                        slowPosition = Math.min(duration, slowPosition + 1000);
                    }
                    slowTimeTip.setText(formatTime(slowPosition) + "/" + formatTime(duration));
                    slowTimeTip.setVisibility(View.VISIBLE);
                    return true;
                }

                return false;
            }
        });

    }

    public String formatTime(long milliseconds) {
        long seconds = (milliseconds / 1000) % 60;
        long minutes = (milliseconds / (1000 * 60)) % 60;
        long hours = (milliseconds / (1000 * 60 * 60)) % 24;

        String timeString = String.format("%02d:%02d:%02d", hours, minutes, seconds);
        return timeString;
    }

   //一些生命周期
    @Override
    protected void onRestart() {
        super.onRestart();
        releasePlayer();
        playVideo(playList.get(selectedPosition).getChapterPath());
        player.setPlayWhenReady(false);
    }

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

    @Override
    protected void onStop() {
        super.onStop();
        if (wakeLock != null && wakeLock.isHeld()) {
            wakeLock.release();
        }
        // 保存播放进度和播放状态
        if(player != null){
            playbackPosition = player.getCurrentPosition();
        }
        releasePlayer();
    }

    //保存activity信息
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("isFullscreen", isFullscreen);
        outState.putLong("playbackPosition", playbackPosition);
        outState.putInt("selectedPosition", selectedPosition);
    }

    // 在此处实现全屏功能
    private void toggleFullscreen() {
        if (isFullscreen) {
            // 退出全屏
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
            videoIntroduce.setVisibility(View.VISIBLE);

        } else {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);//视频填充
            videoIntroduce.setVisibility(View.GONE);
        }
        isFullscreen = !isFullscreen;
    }


    //释放视频资源
    private void releasePlayer() {
        if (player != null) {
            player.release();
            player = null;
        }
    }

   
    //剧集列表
    public class EpisodeAdapter extends RecyclerView.Adapter<EpisodeAdapter.ViewHolder> {

        private List<PlayItem> episodes; // 剧集列表数据

        public EpisodeAdapter(List<PlayItem> episodes) {
            this.episodes = episodes;
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_episode, parent, false);
            return new ViewHolder(view);
        }

        public class ViewHolder extends RecyclerView.ViewHolder {

            private TextView textViewNum;

            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                textViewNum = itemView.findViewById(R.id.textView_num);
            }

        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            PlayItem episode = episodes.get(position);
            holder.textViewNum.setText(episode.getTitle());

            // 设置默认选中第一项
            if (position == selectedPosition) {
                holder.textViewNum.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.blue));
            } else {
                holder.textViewNum.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.black));
            }

            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    PlayItem playItem = episodes.get(position);
                    releasePlayer();
                    playVideo(playItem.getChapterPath());

                    selectedPosition = position;
                    textView.setText(videoTitle+playList.get(position).getTitle());
                    // 设置选中项字体颜色为选中颜色
                    for (int i = 0; i < getItemCount(); i++) {
                        ViewHolder viewHolder = (ViewHolder) recyclerView.findViewHolderForAdapterPosition(i);
                        if (viewHolder != null) {
                            viewHolder.textViewNum.setTextColor(ContextCompat.getColor(viewHolder.itemView.getContext(), R.color.black));
                        }
                    }
                    holder.textViewNum.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.blue));
                }
            });
        }

        @Override
        public int getItemCount() {
            return episodes.size();
        }

    }


}

 以上的getData方法是我自定义的获取视频资源的。

需要注意的是:

1.要实现全屏播放,如果不另外添加设置的话,横屏的时侯activity会重新创建,从而影响视频要重新加载。

在AndroidManifest.xml文件你的播放视频的activity添加以下设置:

android:configChanges="orientation|screenSize"

比如我的是PlayVideoActivity:

<activity android:name=".PlayVideoActivity"
    android:configChanges="orientation|screenSize"/>

2.注意应用切到后台时在生命周期的处理,不然再从后台进入前台有影响。我是切到后台时保存视频的进度,重新进入前台后恢复进度。

3.视频状态是播放中要禁用手机自动锁屏,不然看着看着手机就熄屏了。我的是虽然不会自动锁屏了,但是亮度还是会降低。

总之,写的很粗略,希望大佬能指正。 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值