字节(抖音)小游戏录屏分享,录屏播放功能

本文详细介绍了在cocoscreator2.4.x中实现字节小游戏的录屏和播放功能。通过`byteDanceManager`类,实现了游戏的录屏、暂停、恢复、停止及分享功能,并提供了录屏资源的获取。此外,还展示了如何使用离屏video进行录屏资源的播放。注意,这些功能需在真机上进行调试。
摘要由CSDN通过智能技术生成

本文实现字节小游戏的录屏分享以及录屏播放功能(cocoscreator 2.4.x)

官方文档: 字节小游戏文档
项目地址: git地址

  • 效果演示请添加图片描述
演示中主要分2个功能。其一是游戏录屏,二是录屏播放。
游戏录屏
  • 代码内容

export class byteDanceManager {

    private static _instance = null;
    static instance(): byteDanceManager {
        if(!byteDanceManager._instance) {
            byteDanceManager._instance = new byteDanceManager();
        }
        return byteDanceManager._instance;
    }

    tt: any = null;

    videoHasStop: boolean = false;
    videoHasEnd: boolean = false;

    private gameRecorder: any = null;//屏幕录制管理器
    private totalRecord: number = 300;//总共录屏时间
    private recordTime: number = 0;//已经录屏时间
    private recordTimer: any = null;//录屏定时器
    private videoPath: string = '';
    private recentAdTime: Date = new Date();//上次观看广告时间,用于处理CD


    init() {
        this.tt = window["tt"];
    }

    shareGame() {

    }

    shareAppFunc(title: string, image: string, onSuccess: Function, onFail: Function){
        this.tt.shareAppMessage({
            title: title,
            imageUrl: image,
            success: () => {
                onSuccess && onSuccess();
            },
            fail: (e) => {
                onFail && onFail();
            }
        })
    }

    shareRecordVideo(title: string, videoPath: string, onSuccess: Function, onFail: Function) {
        this.tt.shareAppMessage({
            title: title,
            channel: "video",
            extra: {
                videoTopics: [],
                hashtag_list: [],
                videoPath: videoPath,
                withVideoId: true,
            },
            success: (res) => {
                onSuccess && onSuccess();
            },
            fail: (e) => {
                if(this.checkAppName()){
                    onSuccess && onSuccess();
                }else{
                    onFail && onFail();
                }
            }
        })
    }

    private checkAppName(){
        //当前今日头条ios无法获得分享成功回调
        let result = false;
        this.tt.getSystemInfo({
            success: (res) => {
                if(res.platform === 'ios' && res.appName === 'Toutiao'){
                    result = true
                }
            },
            fail: (e) => {
                console.log("getSystemInfo fail: ", e);
            }
        })
        return result;
    }

	//录屏
    startRecord(callback: Function){
        if(!this.tt) {
            callback && callback();
            return;
        }
        if(!this.gameRecorder){
            this.gameRecorder = this.tt.getGameRecorderManager();
        }
        this.videoHasEnd = false;
        this.videoHasStop = false;
        this.gameRecorder.start({duration: this.totalRecord});
        this.gameRecorder.onStart(() => {
            this.recordTime = 0;
            this.recordTimer = setInterval(() => {
                this.recordTime++;
            }, 1000);
            callback && callback();
        });

        this.gameRecorder.onResume(() => {
            this.recordTimer = setInterval(() => {
                this.recordTime++;
            }, 1000)
        });

        this.gameRecorder.onPause(() => {
            clearInterval(this.recordTimer);
        });

        this.gameRecorder.onStop((res) => {
            this.videoHasEnd = true;
            this.videoPath = res.videoPath;
            console.log("this.videoPath:", this.videoPath);
            clearInterval(this.recordTimer);
            this.recordTime = 0;
        });

        this.gameRecorder.onError((e) => {
            console.log("gameRecord error: ", e);
        })
    }

    resumeRecord(callback: Function){
        if(!this.tt) {
            return;
        }
        if(!this.gameRecorder){
            console.log("gameRecorder is null, resumeRecord fail");
        }else{
            this.gameRecorder.resume();
            callback && callback();
        }
    }

    pauseRecord(callback: Function){
        if(!this.tt) {
            return;
        }
        if(!this.gameRecorder){
            console.log("gameRecorder is null, pauseRecord fail");
        }else{
            this.gameRecorder.pause();
            callback && callback();
        }
    }

    stopRecord(callback: Function){
        if(!this.tt) {
            return;
        }
        if(!this.gameRecorder){
            console.log("gameRecorder is null, stopRecord fail")
        }else{
            this.gameRecorder.stop();
            callback && callback();
        }
    }

    getVideoPath(){
        return this.videoPath;
    }


    public onShareFunc(text: string, image: string, callback: Function){
        let channel = "";
        this.tt.getSystemInfo({
            success: (res) => {
                console.log("getSystemInfo success: ", res);
                if (res.appName === 'Douyin') {
                    channel = "invite";
                } else if(res.appName === 'Toutiao'){
                    channel = 'article';
                } else {
                    channel = '';
                }
            },
            fail: (res) => {
                console.log("getSystemInfo fail: ", res);
                channel = '';
            }
        })
        this.tt.shareAppMessage({
            // templateId: "",
            title: text,
            channel: channel,
            imageUrl: image,
            success: ()=>{
                callback && callback();
            },
            fail: ()=>{
                callback && callback();
            },
            complete: ()=>{
            }
        })
    }
}

export const btMgr = byteDanceManager.instance();

	btMgr.startRecord(()=>{})//开始录屏方法
	btMgr.stopRecord(()=>{})//停止录屏方法
录屏播放
  • 录屏播放需要在录屏停止后,从录屏停止监听中获得录屏资源地址,使用字节的离屏video渲染出来。
  • 代码内容
	let videoPath = btMgr.getVideoPath();//获取录屏文件
//播放录屏
const {ccclass, property} = cc._decorator;

@ccclass
export default class ShareVideo extends cc.Component {

    
    @property(cc.Node)
    private parentNode: cc.Node = null;
    // LIFE-CYCLE CALLBACKS:

    // onLoad () {}

    private cameraNode;
    private video;
    private videoTexture: cc.Texture2D;

    start () {

    }

    initVideo(video: string){
        this.showcamera(video);
    }

    private showcamera(video: string) {
        this.cameraNode = new cc.Node();
        this.cameraNode.width = this.parentNode.width;
        this.cameraNode.height = this.parentNode.height;
        this.cameraNode.addComponent(cc.Sprite)
        this.parentNode.addChild(this.cameraNode);
        if (typeof window['tt'] !== 'undefined') {
            this.playVideo(video);
        }
    }

    private playVideo(video: string) {
        this.video = window['tt'].createOffscreenVideo();
            // 传入视频src
            this.video.src = video;
            this.video.onCanplay(() => {
                if(this.node && this.node.isValid){
                    this.video.play();
                    this.videoTexture = new cc.Texture2D();
                    this.videoTexture.initWithElement(this.video);
                    this.videoTexture.handleLoadedTexture();
                    this.cameraNode.getComponent(cc.Sprite).spriteFrame = new cc.SpriteFrame(this.videoTexture);
                    this.cameraNode.width = this.parentNode.width;
                    this.cameraNode.height = this.parentNode.height;

                    // this.cameraNode.width = cc.view.getVisibleSize().width;
                    // this.cameraNode.height = this.video.videoHeight / this.video.videoWidth * this.cameraNode.width;
                }
            });
            this.video.onCandraw(()=>{

            })
            this.video.onPlay(()=>{
                console.log("开始录屏");

            });
            this.video.onEnded(()=>{
            });
            this.video.onPause(()=>{
            });
        }

    update(dt) {
        if (this.video && this.videoTexture) {
            this.videoTexture.update({
                image: this.video,
                flipY: false
            });
        }
    }
}
到这里基本就完成了录屏和录屏播放的功能了。已经可以打包到字节平台调试,如果要录屏分享可以自接调用btMgr.shareRecordVideo()方法并传入相关参数即可。注意录屏以及播放功能,只能使用真机调试。
以下是一个简单的 Android MediaCodec 录屏实现的完整代码示例,可以记录屏幕并保存为 MP4 文件。 ``` public class ScreenRecorder { private static final String TAG = "ScreenRecorder"; private static final String MIME_TYPE = "video/avc"; private static final int FRAME_RATE = 30; // 帧率 private static final int I_FRAME_INTERVAL = 1; // I 帧间隔 private static final int BIT_RATE = 6000000; // 比特率 private static final int REQUEST_CODE = 1; private static int width; private static int height; private static int dpi; private MediaProjection mediaProjection; private MediaCodec mediaCodec; private MediaMuxer mediaMuxer; private Surface surface; private VirtualDisplay virtualDisplay; private boolean isRecording; private String outputFilePath; private Context context; public ScreenRecorder(Context context) { this.context = context; } public void startRecording(int resultCode, Intent data) { if (isRecording) { Log.e(TAG, "Already recording"); return; } mediaProjection = createMediaProjection(resultCode, data); if (mediaProjection == null) { Log.e(TAG, "MediaProjection is null"); return; } width = context.getResources().getDisplayMetrics().widthPixels; height = context.getResources().getDisplayMetrics().heightPixels; dpi = context.getResources().getDisplayMetrics().densityDpi; mediaCodec = createMediaCodec(); mediaMuxer = createMediaMuxer(); mediaCodec.start(); virtualDisplay = createVirtualDisplay(); isRecording = true; } public void stopRecording() { if (!isRecording) { Log.e(TAG, "Not recording"); return; } isRecording = false; if (virtualDisplay != null) { virtualDisplay.release(); virtualDisplay = null; } if (mediaCodec != null) { mediaCodec.stop(); mediaCodec.release(); mediaCodec = null; } if (mediaProjection != null) { mediaProjection.stop(); mediaProjection = null; } if (mediaMuxer != null) { mediaMuxer.stop(); mediaMuxer.release(); mediaMuxer = null; } } private MediaProjection createMediaProjection(int resultCode, Intent data) { return ((MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE)) .getMediaProjection(resultCode, data); } private MediaCodec createMediaCodec() { MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL); MediaCodec codec = null; try { codec = MediaCodec.createEncoderByType(MIME_TYPE); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = codec.createInputSurface(); } catch (IOException e) { e.printStackTrace(); } return codec; } private MediaMuxer createMediaMuxer() { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US); String dateTime = dateFormat.format(new Date()); String fileName = "screen_record_" + dateTime + ".mp4"; outputFilePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + File.separator + fileName; MediaMuxer muxer = null; try { muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (IOException e) { e.printStackTrace(); } return muxer; } private VirtualDisplay createVirtualDisplay() { return mediaProjection.createVirtualDisplay(TAG, width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null); } public boolean isRecording() { return isRecording; } public String getOutputFilePath() { return outputFilePath; } } ``` 使用该类,你只需要在你的 Activity 中调用 startRecording() 方法来开始录制,调用 stopRecording() 方法来停止录制。录制完成后,你可以通过调用 getOutputFilePath() 方法来获得输出文件的路径。 以下是一个简单的使用示例: ``` public class MainActivity extends AppCompatActivity { private static final int REQUEST_CODE = 1; private ScreenRecorder screenRecorder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); screenRecorder = new ScreenRecorder(this); findViewById(R.id.start_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startRecording(); } }); findViewById(R.id.stop_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { stopRecording(); } }); } private void startRecording() { Intent mediaProjectionIntent = ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE)) .createScreenCaptureIntent(); startActivityForResult(mediaProjectionIntent, REQUEST_CODE); } private void stopRecording() { screenRecorder.stopRecording(); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE && resultCode == RESULT_OK && data != null) { screenRecorder.startRecording(resultCode, data); } } } ``` 补充说明:该代码仅仅是一个简单的示例,可能存在许多潜在问题和优化空间。请在实际使用中自行测试和改进。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蟹 !

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值