打赏

自己搭建简单的直播平台 自己windows电脑(不推荐)android 推流 拉流的代码

    implementation 'com.github.pedroSG94.rtmp-rtsp-stream-client-java:rtplibrary:1.7.7'
    implementation 'com.github.NodeMedia:NodeMediaClient-Android:2.9.7'
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_example_rtmp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:layout_marginBottom="5dp">
        <SurfaceView
            android:id="@+id/surfaceView_top"
            android:layout_width="match_parent"
            android:layout_height="300dp"/>
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/url_push"
            android:textColor="@color/white"
            android:hint="@string/hint_rtmp_push"
            android:textColorHint="@color/gray"
            android:gravity="center"
            android:layout_margin="10dp"
            android:theme="@style/EditTextStyle"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_alignParentBottom="true"
            android:layout_margin="20dp"
            android:gravity="center"
            >

            <Button
                android:text="@string/start_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/b_start_stop"
                android:background="@color/colorAccent"
                android:minHeight="0dp"
                android:minWidth="0dp"
                android:layout_marginRight="10dp"
                android:theme="@style/ButtonStyle"/>

            <Button
                android:id="@+id/switch_camera"
                android:text="@string/switch_camera_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/colorAccent"
                android:minHeight="0dp"
                android:minWidth="0dp"
                android:theme="@style/ButtonStyle"/>
        </LinearLayout>
    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:layout_marginTop="5dp">
        <cn.nodemedia.NodePlayerView
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:id="@+id/surfaceView_bottom"/>
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/url_pull"
            android:textColor="@color/white"
            android:hint="@string/hint_rtmp_pull"
            android:textColorHint="@color/gray"
            android:gravity="center"
            android:layout_margin="10dp"
            android:theme="@style/EditTextStyle"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_alignParentBottom="true"
            android:layout_margin="20dp"
            android:gravity="center"
            >
            <Button
                android:text="@string/start_player"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/start_pull"
                android:background="@color/colorAccent"
                android:minHeight="0dp"
                android:minWidth="0dp"
                android:theme="@style/ButtonStyle"/>
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>
package com.aipeople.by.video;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import com.aipeople.by.R;
import com.pedro.encoder.input.video.CameraOpenException;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;

import net.ossrs.rtmp.ConnectCheckerRtmp;

import cn.nodemedia.NodePlayer;
import cn.nodemedia.NodePlayerDelegate;
import cn.nodemedia.NodePlayerView;

public class VideoActivity extends AppCompatActivity
        implements ConnectCheckerRtmp, View.OnClickListener, SurfaceHolder.Callback, NodePlayerDelegate {
    private final String[] PERMISSIONS = {
            Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    private RtmpCamera1 rtmpCamera1;
    private Button button;
    private Button switchCamera;
    private Button pullBut;
    private EditText urlpush;
    private EditText urlpull;
    private NodePlayer np;
    SurfaceView surfaceViewTop;
    private String[] options = new String[]{":fullscreen", ":network-caching=350"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video);
        if (!hasPermissions(this, PERMISSIONS)) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
        }
        init();

        rtmpCamera1 = new RtmpCamera1(surfaceViewTop, this);
        rtmpCamera1.setReTries(10);
        surfaceViewTop.getHolder().addCallback(this);
//        拉流
        np = new NodePlayer(this);
        NodePlayerView npv = findViewById(R.id.surfaceView_bottom);
        npv.setRenderType(NodePlayerView.RenderType.SURFACEVIEW);
        npv.setUIViewContentMode(NodePlayerView.UIViewContentMode.ScaleAspectFill);
        np.setPlayerView(npv);
        np.setNodePlayerDelegate(this);
        np.setHWEnable(true);


    }
    private void init(){
        surfaceViewTop = findViewById(R.id.surfaceView_top);
        button =  findViewById(R.id.b_start_stop);
        pullBut = findViewById(R.id.start_pull);
        switchCamera = findViewById(R.id.switch_camera);
        urlpull = findViewById(R.id.url_pull);
        urlpush = findViewById(R.id.url_push);
        switchCamera.setOnClickListener(v -> {
            try {
                rtmpCamera1.switchCamera();
            } catch (CameraOpenException e) {
                Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
        button.setOnClickListener(v -> {
            if (!rtmpCamera1.isStreaming()) {
                if (rtmpCamera1.isRecording()
                        || rtmpCamera1.prepareVideo(640, 480, 30, 1200 * 1024, false, 0) &&  rtmpCamera1.prepareAudio()) {
                    button.setText(R.string.stop_button);
                    rtmpCamera1.startStream(urlpush.getText().toString());
                } else {
                    Toast.makeText(this, "Error preparing stream, This device cant do it",
                            Toast.LENGTH_SHORT).show();
                }
            } else {
                button.setText(R.string.start_button);
                rtmpCamera1.stopStream();
            }
        });
        pullBut.setOnClickListener(v -> {
            if(!np.isPlaying()){
                np.setInputUrl(urlpull.getText().toString());
                np.start();
                pullBut.setText(getString(R.string.stop_player));
            }else {
                np.stop();
                pullBut.setText(getString(R.string.start_player));
            }
        });
    }
    private boolean hasPermissions(Context context, String... permissions) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) {
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(context, permission)
                        != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
        }
        return true;
    }

//拉流事件回调
    /**
     * 事件回调
     * @param nodePlayer 对象
     * @param event 事件状态码
     * @param msg   事件描述
     */
    @Override
    public void onEventCallback(NodePlayer nodePlayer, int event, String msg) {
        Log.i("NodeMedia.NodePlayer","onEventCallback:"+event+" msg:"+msg);

        switch (event) {
            case 1000:
                // 正在连接视频
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(VideoActivity.this, "Connecting", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case 1001:
                // 视频连接成功
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(VideoActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
            case 1002:
                // 视频连接失败 流地址不存在,或者本地网络无法和服务端通信,回调这里。5秒后重连, 可停止
//                nodePlayer.stopPlay();
                break;
            case 1003:
                // 视频开始重连,自动重连总开关
//                nodePlayer.stopPlay();
                break;
            case 1004:
                // 视频播放结束
                break;
            case 1005:
                // 网络异常,播放中断,播放中途网络异常,回调这里。1秒后重连,如不需要,可停止
//                nodePlayer.stopPlay();
                break;
            case 1006:
                //RTMP连接播放超时
                break;
            case 1100:
                // 播放缓冲区为空
//                System.out.println("NetStream.Buffer.Empty");
                break;
            case 1101:
                // 播放缓冲区正在缓冲数据,但还没达到设定的bufferTime时长
//                System.out.println("NetStream.Buffer.Buffering");
                break;
            case 1102:
                // 播放缓冲区达到bufferTime时长,开始播放.
                // 如果视频关键帧间隔比bufferTime长,并且服务端没有在缓冲区间内返回视频关键帧,会先开始播放音频.直到视频关键帧到来开始显示画面.
//                System.out.println("NetStream.Buffer.Full");
                break;
            case 1103:
//                System.out.println("Stream EOF");
                // 客户端明确收到服务端发送来的 StreamEOF 和 NetStream.Play.UnpublishNotify时回调这里
                // 注意:不是所有云cdn会发送该指令,使用前请先测试
                // 收到本事件,说明:此流的发布者明确停止了发布,或者因其网络异常,被服务端明确关闭了流
                // 本sdk仍然会继续在1秒后重连,如不需要,可停止
//                nodePlayer.stopPlay();
                break;
            case 1104:
                //解码后得到的视频高宽值 格式为:{width}x{height}
                break;
            default:
                break;
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 停止播放
         */
        np.stop();

        /**
         * 释放资源
         */
        np.release();
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (rtmpCamera1.isStreaming()) {
            rtmpCamera1.stopStream();
            button.setText(getResources().getString(R.string.start_button));
        }
        rtmpCamera1.stopPreview();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {


            default:
                break;
        }
    }
    @Override
    public void onConnectionSuccessRtmp() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(VideoActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onConnectionFailedRtmp(@NonNull final String reason) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (rtmpCamera1.reTry(5000, reason)) {
                    Toast.makeText(VideoActivity.this, "Retry", Toast.LENGTH_SHORT)
                            .show();
                } else {
                    Toast.makeText(VideoActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
                            .show();
                    rtmpCamera1.stopStream();
                    button.setText(R.string.start_button);
                }
            }
        });
    }

    @Override
    public void onNewBitrateRtmp(long bitrate) {

    }

    @Override
    public void onDisconnectRtmp() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(VideoActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onAuthErrorRtmp() {

    }

    @Override
    public void onAuthSuccessRtmp() {

    }
}

 

posted @ 2025-04-14 10:11  张学涛  阅读(33)  评论(0)    收藏  举报