import { sys, _decorator } from 'cc'; import { MiniGamePlatform } from '../platform/MiniGamePlatform'; const IGG_URL: string = 'https://gate.snssdk.com/developer/api/get_top_video_ids_by_like'; const NEW_URL: string = 'https://gate.snssdk.com/developer/api/get_top_video_ids_by_time'; const { ccclass, property } = _decorator; /** * 头条录屏相关。 * 鸿蒙:视频挂载/录屏分享等开放能力需 canIUse,不支持则降级,禁止直接裸调 tt API。 * @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/open-capabilities/general-capabilities/miniapp-harmony-adapter */ @ccclass export default class TTRecording { static Ins: TTRecording = new TTRecording(); private _recorder: any; private _videoPath: string = ''; private _iTime: number = 0; private _timeOutId; private _isStop: boolean = true; private _appid = ``; private _game_name = ``; getVideoPath() { return this._videoPath; } getVideoTime() { return this._iTime; } getStatus() { return !this._isStop; } init(data) { MiniGamePlatform.init(); if (data && data.appid) this._appid = data.appid; if (data && data.gameName) this._game_name = data.gameName; // 鸿蒙/低版本:录屏管理器不可用则跳过,避免同步 throw 阻塞启动 if (!MiniGamePlatform.canIUse('getGameRecorderManager')) { console.warn('[TTRecording] getGameRecorderManager unavailable (harmony/low SDK)'); return; } const tt = MiniGamePlatform.pf as any; if (!tt?.getGameRecorderManager || this._recorder != null) return; const self = this; try { this._recorder = tt.getGameRecorderManager(); } catch (e) { console.warn('[TTRecording] getGameRecorderManager throw', e); this._recorder = null; return; } this._recorder.onStart((res) => { console.log('录屏开始'); self._isStop = false; self._videoPath = null; self._iTime = 0; clearInterval(self._timeOutId); self._timeOutId = setInterval(() => { !self._isStop && self._iTime++; }, 1000); }); this._recorder.onStop((res) => { self._isStop = true; const time = self._iTime >= 30 ? 30 : self._iTime; if (time < 3) return; console.log(time + 'onStop=', res, time); self._recorder.clipVideo({ path: res.videoPath, timeRange: [time, 0], success(clipRes) { console.log(time + clipRes.videoPath); self._videoPath = clipRes.videoPath; }, fail(e) { console.error(e); }, }); }); } startVideo() { if (this._recorder) { this._recorder.stop(); this._recorder.start({ duration: 300, }); this._isStop = false; } } pauseVideo() { this._recorder && this._recorder.pause(); } resumeVideo() { this._recorder && this._recorder.resume(); if (this._isStop) { this.startVideo(); } } stopVideo() { if (this._recorder && !this._isStop) { this._recorder.stop(); this._isStop = true; } } shareVideo(successFunc: Function) { this._timeOutId > -1 && clearInterval(this._timeOutId); const path = this._videoPath; if (this._iTime < 3) { console.log('录屏失败:录屏时长低于 3 秒'); successFunc && successFunc(false); return; } if (!this._videoPath) { successFunc && successFunc(false); console.log('shareVideo 分享失败', path, this._iTime); return; } // 官方:不支持的 API 先 canIUse;鸿蒙视频分享常不可用,降级不阻塞 if (!MiniGamePlatform.isDouyinVideoOpenCapabilityAvailable('shareAppMessage')) { console.warn('[TTRecording] shareAppMessage unavailable on this OS'); MiniGamePlatform.pf?.showToast?.({ title: '当前系统暂不支持分享录屏', icon: 'none' }); successFunc && successFunc(false); return; } console.log('shareVideo 分享开始', this._iTime); this._videoPath = null; const gameName = this._game_name; MiniGamePlatform.invokeAsync('shareAppMessage', { channel: 'video', extra: { videoPath: path, videoTopics: [gameName], withVideoId: true, }, success() { successFunc && successFunc(true); console.log('分享视频成功'); }, fail(e: { errMsg?: string; errNo?: number }) { successFunc && successFunc(false); console.log('分享视频失败', e?.errMsg, e?.errNo); if (e?.errNo === 10302) { MiniGamePlatform.pf?.showToast?.({ title: '当前系统暂不支持分享录屏', icon: 'none' }); return; } if (e?.errMsg && e.errMsg.indexOf('short') != -1) { console.log('分享视频失败,视频时间过短'); MiniGamePlatform.pf?.showToast?.({ title: 'video time too short', icon: 'fail' }); } }, }); } getVideoDiggRank(token: string, cb: Function) { this.requestVideoRank(token, IGG_URL, cb); } getVideoNewRank(token: string, cb: Function) { this.requestVideoRank(token, NEW_URL, cb); } requestVideoRank(token: string = '', url: string = '', cb: Function) { if (!MiniGamePlatform.canIUse('request')) { cb && cb(null); return; } MiniGamePlatform.invokeAsync('request', { url: url, method: 'POST', data: { app_id: this._appid, number_of_top: 100, access_token: token, }, success: (res: { data?: { err_no?: number; data?: unknown } }) => { if (res?.data?.err_no !== 0) { cb && cb(null); console.log('视频排行数据获取失败====='); } else { console.log(res); cb && cb(res.data.data); } }, fail: () => { cb && cb(null); }, }); } /** 排行视频点击跳转(鸿蒙/低版本不支持则 toast 降级) */ navigateToVideo(videoId: number) { if (!MiniGamePlatform.isDouyinVideoOpenCapabilityAvailable('navigateToVideoView')) { console.warn('[TTRecording] navigateToVideoView unavailable on this OS'); MiniGamePlatform.pf?.showToast?.({ title: '当前系统暂不支持打开视频', icon: 'none' }); return; } MiniGamePlatform.invokeAsync('navigateToVideoView', { videoId: videoId, success: () => { console.log('navigateToVideoView done'); }, fail: (err: { errCode?: number; errNo?: number }) => { if (err?.errNo === 10302 || err?.errCode === 1006) { MiniGamePlatform.pf?.showToast?.({ title: err?.errNo === 10302 ? '当前系统暂不支持打开视频' : 'something wrong with your network', }); } }, }); } request(url: string = '', data: any = null, method: string = '', success: Function = null, fail: Function = null, complete: Function = null) { if (sys.platform == sys.Platform.DESKTOP_BROWSER) return; const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { const response = xhr.responseText; if (xhr.status >= 200 && xhr.status < 400) { let result = {}; try { result = JSON.parse(response); } catch (e) { console.error('json parse error ', response); fail && fail(e); } success && success(result); } else { console.error('error ', response); fail && fail(response); } } }; xhr.timeout = 3000; xhr.ontimeout = function (event) { console.error('error ', event); fail && fail(event); }; xhr.open(method, url, true); if (method == 'POST') { const object2Query = function (obj) { const args = []; for (const k in obj) { args.push(k + '=' + obj[k]); } return args.join('&'); }; xhr.open('POST', url); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(object2Query(data)); } else { xhr.send(); } } }