You need to enable JavaScript to run this app.
导航
基础功能
最近更新时间:2024.11.01 11:20:02首次发布时间:2021.02.23 10:42:42

本文为您介绍如何使用 Android 点播 SDK 的基础功能。

播放控制

TTVideoEngine 提供了类似于 Android 系统 MediaPlayer 的播放控制的方法。

播放

调用 play 方法开始或恢复播放视频。示例代码如下所示。

ttVideoEngine.play(); // 开始播放或恢复播放

暂停

调用 pause 方法暂停播放视频。再次调用 play 方法,状态可由暂停恢复到播放。示例代码如下所示。

ttVideoEngine.pause(); // 暂停播放
ttVideoEngine.play(); // 播放器由暂停恢复到播放状态

Seek 到指定位置播放

调用 seekTo 方法 Seek 到指定位置进行播放,实现拖拽进度条到指定时间开始播放的功能。示例代码如下所示。

/** 演示 seek 到 1 秒的位置 */
ttVideoEngine.seekTo(1000, new SeekCompletionListener() {
    @Override
    public void onCompletion(boolean success) {
        // seek 操作完成后回调。success 参数标识本次 seek 是否成功。true:成功
    }
});

ttVideoEngine.setVideoEngineInfoListener(new VideoEngineInfoListener() {
    @Override
    public void onVideoEngineInfos(VideoEngineInfos videoEngineInfos) {
        if (TextUtils.equals(videoEngineInfos.getKey(), VideoEngineInfos.USING_RENDER_SEEK_COMPLETE)) {
            // seek 渲染完成回调。
        }
    }
});

从指定时间起播

在调用 play 前通过 setStartTime 方法指定开始播放时间点,实现从指定时间开始播放或跳过片头等功能。示例代码如下:

注意

如果您使用预渲染功能,需在 prepare 前设置。

int startPlayPositionMS = 1000; // 单位 MS
ttVideoEngine.setStartTime(startPlayPositionMS); // 从 1 秒钟起播
ttVideoEngine.play();

停止

调用 stop 方法停止播放视频。示例代码如下所示。

ttVideoEngine.stop(); // 停止播放

释放

调用 releaseAsync 方法异步释放播放器实例。示例代码如下所示。

ttVideoEngine.releaseAsync(); // 异步释放播放器实例
boolean isReleased = ttVideoEngine.isReleased(); //获取释放状态
ttVideoEngine = null; // 防止再次调用

说明

  • 当播放器释放后,不应该再调用任何方法。
  • 释放后可以将 TTVideoEngine 设置为 null,防止再次调用。

纯音频播放

点播 SDK 支持在播放视频时,只解码音频而不解码视频,适用于纯音频播放场景。相比您根据自身业务逻辑实现的纯音频播放,SDK 只解码音频会更省电。

注意

该功能仅高级版或企业版支持。请确保您已购买高级版或企业版的 License,详见 License 包管理

// 开启纯音频播放
ttVideoEngine.setRadioMode(true);
// 恢复音视频播放
ttVideoEngine.setRadioMode(false);

显示模式

Android 点播 SDK 支持填充、旋转和镜像等显示模式。

填充模式

视频的比例播放控件的比例不一致,就会造成视频拉伸变形的问题。你可以通过设置 displayModedisPlayView 的参数来设置不同的显示模式。示例代码如下所示。

// 无变形;等比例缩放;画面不被裁剪;可能有黑边
int displayMode1 = TTVideoEngineInterface.IMAGE_LAYOUT_ASPECT_FIT;
// 可能会变形;画面宽高都充满控件;画面不被裁剪;无黑边
int displayMode2 = TTVideoEngineInterface.IMAGE_LAYOUT_TO_FILL;
// 无变形;等比例缩放;画面可能被裁剪;无黑边
int displayMode3 = TTVideoEngineInterface.IMAGE_LAYOUT_ASPECT_FILL;
// 无变形;画面宽充满控件,高按视频比例适配;画面可能被裁剪;可能有黑边。开启 Texturerender 时不生效
int displayMode4 = TTVideoEngineInterface.IMAGE_LAYOUT_ASPECT_FILL_X;
// 无变形;画面高充满控件,宽按视频比例适配;画面可能被裁剪;可能有黑边。开启 Texturerender 时不生效
int displayMode5 = TTVideoEngineInterface.IMAGE_LAYOUT_ASPECT_FILL_Y;

View disPlayView = findViewById(R.id.textureView);
// View disPlayView = findViewById(R.id.surfaceView);

// 参数 disPlayView 传入显示画面的 TextureView 或 SurfaceView
// 参数 displayMode 值见下表
ttvideoEngine.setDisplayMode(disPlayView, displayMode1);

旋转

按照以下步骤实现旋转功能:

  1. 初始化播放器后,调用 play 前,通过 setIntOption 开启 Texture Render。示例代码如下:

    // 在调用 `play` 前设置
    ttvideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_USE_TEXTURE_RENDER, 1);
    
  2. 调用 play 后,调用 setRotation 设置视频显示时的旋转角度。设置后,视频会顺时针旋转。示例代码如下:

    int rotation = 90; // 旋转角度仅支持 0°/90°/180°/270°,其他值无效
    ttvideoEngine.setRotation(rotation); // 设置视频显示时的旋转角度,在调用 `play` 后设置
    

镜像

调用 setMirrorHorizontalsetMirrorVertical 方法设置水平和垂直镜像。

// 设置水平镜像
boolean  mirrorHorizontal = true;
ttVideoEngine.setMirrorHorizontal(mirrorHorizontal);
// 设置垂直镜像
boolean  mirrorVertical = true;
ttVideoEngine.setMirrorVertical(mirrorVertical);

获取视频宽高

TTVideoEngine 触发 onPrepared 回调之后,调用 getVideoWidthgetVideoHeight 方法获取视频的宽高。示例代码如下:

int videoWidth = ttVideoEngine.getVideoWidth();  //获取视频的宽
int videoHeight = ttVideoEngine.getVideoHeight(); // 获取视频的高

截图

  1. 初始化播放器后,调用 play 前,通过 setIntOption 开启 Texture Render。示例代码如下:

    // 在调用 `play` 前设置
    ttvideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_USE_TEXTURE_RENDER, 1);
    
  2. 调用 play 后,通过 snapshot 设置截图回调通知;调用 SnapshotListener 接口中定义的 onSnapShot 方法,通过回调函数来获取并处理视频截图位图、截图的宽度和高度。示例代码如下:

    mVideoEngine.snapshot(new SnapshotListener() {
        @Override
        public void onSnapShot(final Bitmap bitmap, final int with, final int height) {
    
            }
       });
    

获取播放信息

Android 点播 SDK 支持获取当前播放进度、播放时长、缓存进度和缓存进度回调等播放信息。

获取当前播放进度

  • 调用 getCurrentPlaybackTime 方法获取当前播放位置。示例代码如下所示。

    int currentPostion = ttVideoEngine.getCurrentPlaybackTime(); // 当前播放位置获取,单位 MS
    
  • 通过 onCurrentPlaybackTimeUpdate 回调获取定时进度。示例代码如下所示。

    // 通过设置进度回调间隔开启回调,play 之前设置
    ttVideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_POSITION_UPDATE_INTERVAL, 200); 
    ttVideoEngine.setVideoEngineCallback(new VideoEngineCallback() {
        // ...省略其余实现方法
        @Override
        public void onCurrentPlaybackTimeUpdate(TTVideoEngine engine, int currentPlaybackTime) {
            Log.v("VideoPlay", "onCurrentPlaybackTimeUpdate " + engine + " " + currentPlaybackTime);
        }
    });
    

获取播放时长

调用 getDuration 方法获取播放时长。示例代码如下所示。

int duration = ttVideoEngine.getDuration(); // 视频时长获取,单位 MS

获取缓存进度

  • 调用 getLoadedProgress 方法获取缓存进度。示例代码如下所示。

    int loadedProgress = ttVideoEngine.getLoadedProgress(); // 缓存进度获取,取值 1-100
    
  • 通过 onBufferingUpdate 回调获取缓存进度。示例代码如下所示。

    ttVideoEngine.setListener(new VideoEngineListener() {
      // ...省略其余实现方法
      @Override
      public void onBufferingUpdate(TTVideoEngine engine, int percent) {
          Log.v("VideoPlay", "onBufferingUpdate " + engine + " " + percent);
          // 缓存进度回调,可用于展示播放进度条上的二级缓存进度,取值1-100
      }
    });
    

循环播放

调用 setLooping 方法并取值为 true 开启循环播放。示例代码如下所示。

ttVideoEngine.setLooping(true); // 开启循环播放
boolean isLooping = ttVideoEngine.isLooping(); // 查询是否开启循环播放

倍速播放

调用 setPlaybackParams 方法设置倍速。默认值为 1,取值范围为(0,3]。示例代码如下所示。

PlaybackParams params = new PlaybackParams();
params.setSpeed(1f); // 默认为 1 倍速,取值范围(0, 3]
ttVideoEngine.setPlaybackParams(params);

设置音量

Android 点播 SDK 支持音频焦点、调节音量和静音等音量设置的功能。

静音

调用 setIsMute 方法并取值为 true 实现静音。示例代码如下所示。

ttVideoEngine.setIsMute(true); // 静音
boolean isMute = ttVideoEngine.isMute(); // 获取是否静音

调节音量

调节音量包含调节系统音量、调节播放音量方式。

调用 setVolume 方法设置系统左右声道音量。示例代码如下:
float maxVolume = ttVideoEngine.getMaxVolume();  // 获取最大音量
float currentVolume = ttVideoEngine.getVolume(); // 获取当前音量
ttVideoEngine.setVolume(1f, 1f); // 设置左右声道音量,取值范围为 [0,maxVolume]

音频焦点

点播 SDK 内部不处理音频焦点,需接入方需监听 AudioManager.OnAudioFocusChangeListener 来处理音频焦点的获取和释放。参考官方文档:管理音频焦点

设置清晰度

使用 VideoID 视频源播放时,服务端会下发多个清晰度的播放地址。当播放器接收到 onFetchedVideoInfo 回调时,就可以调用 supportedResolutionTypes 方法获取到所有清晰度的 Resolution[] 数组。您可用该数组做清晰度列表的展示和清晰度逻辑选择。

获取当前清晰度

调用 getCurrentResolution 方法获取当前清晰度。示例代码如下所示。

// 获取当前清晰度
Resolution currentResolution = ttVideoEngine.getCurrentResolution();

获取清晰度列表

ttVideoEngine.setVideoInfoListener(new VideoInfoListener() {
    @Override
    public boolean onFetchedVideoInfo(VideoModel videoModel) {
       // 获取视频数据成功回调
        Log.v("VideoPlay", "onFetchedVideoInfo " + videoModel);
        if (videoModel == null) return false;
        
        // 获取当前 VideoModel 的清晰度数组,可用于清晰度列表展示
        Resolution[] resolutions = ttVideoEngine.supportedResolutionTypes();
          
        return false;
    }
});

切换清晰度

  1. 设置起播清晰度:在播放之前预设值清晰度信息,起播设置清晰度可能不在支持清晰度列表,通过调用 findDefaultResolution 确定最终的启播清晰度。

    // 默认清晰度 360p
    Resolution defaultResolution = Resolution.Standard;
    // VideoModel 中可能不包含 defaultResolution, 使用 findDefaultResolution 找出与 defaultResolution 最接近的清晰度。
    Resolution resolution = TTVideoEngine.findDefaultResolution(videoModel, defaultResolution);
    // 设置选定的起播清晰度
    ttVideoEngine.configResolution(resolution);
    
  2. 起播后,需要明确支持哪些清晰度的视频,切换指定清晰度档位。

    // 获取当前可用的清晰度列表
    Resolution[] resolutions = ttVideoEngine.supportedResolutionTypes();
    // 确定要切换的清晰度,这里逻辑简写,业务根据自身逻辑选定相应的清晰度
    Resolution resolution = resolutions[0];
    // 设置选定的起播清晰度
    ttVideoEngine.configResolution(resolution);
    

Resolution 枚举如下表所示。

key

视频清晰度

音频清晰度

视频描述

音频描述

Standard

360p

medium

标清

普通音质

High

480p

higher

高清

高音质

SuperHigh

720p

highest

超清

音乐音质

ExtremelyHigh

1080p

original

1080P

原画

TwoK

2k

2K

此档位对音频不生效

FourK

4k

4K

此档位对音频不生效

Auto

自动调节

DASH 支持根据网速动态调节分辨率

此档位对音频不生效

设置 HLS 过期时间

通过不同方式播放 HLS 视频,设置过期时间的方法不同。

DirectUrl 方式

如果您通过 DirectUrl 方式播放 HLS 视频,在设置播放源和预加载时均可以通过 urlExpiredTimes 参数设置过期时间。

设置播放源

在设置 DirectUrl 播放源时,创建 StrategySource 时传入过期时间,然后调用 setStrategySource 去播放。示例代码如下所示:

StrategySource directUrlSource = new DirectUrlSource.Builder()
        .setVid(vid)
        .addItem(new DirectUrlSource.UrlItem.Builder()
                .setUrl(url)
                .setCacheKey(cacheKey)
                .setUrlExpires(new String[]{"xxx"}) // 单位为秒
                .build())
        .build();
mEngine.setStrategySource(directUrlSource); 

预加载

在预加载播放场景下,您可在创建 DirectUrlSource 时,通过 setUrlExpires 传入过期时间,然后按照预加载流程调用 addTask 去进行预加载。示例代码如下所示:

DirectUrlSource directUrlSource = new DirectUrlSource.Builder()
    .setVid(vid)
    .addItem(new DirectUrlSource.UrlItem.Builder()
            .setUrl(url)
            .setCacheKey(cacheKey)
            .setUrlExpires(new String[]{"xxx"}) // 单位为秒
            .build())
    .build();
PreloaderURLItem preloaderUrlItem =
        mFactory.createUrlItem(directUrlSource, preloadSize);
preloaderUrlItem.setCallBackListener(new PreloadCallback(vid, this));
TTVideoEngine.addTask(preloaderUrlItem);

判断过期时间是否设置成功

您可以通过 Debug 调试状态下查看日志,确认播放地址中是否包含 hlsproxyQuery 关键字。示例如下:

hlsproxyQuery=expirteTime%03Dxxxxxxxxxx

Vid 方式

如果您通过 Vid 方式播放 HLS 视频,客户端无需额外设置,只需确保 GetPlayInfo 接口中返回的 VideoModel 包含过期时间信息即可。

设置业务类型

业务类型(tag)用于区分同一应用(appid)内不同类型的音视频。可以根据业务需要按视频场景、视频时长等划分,比如沉浸式 feed 流、短视频视频、长视频等。调用 setTag 方法设置业务类型,代码示例如下所示。

ttVideoEngine.setTag("tag"); // 设置 Tag

设置自定义标签

自定义标签(subtag)用于区分同一业务类型下更为细分的音视频类型,比如加密视频、非加密视频、音频等。调用 setSubTag 方法设置自定义标签,代码示例如下所示。

ttVideoEngine.setSubTag("subtag"); //设置 SubTag

屏幕常亮

调用 View#setKeepScreenOn(boolean) 设置屏幕保持常亮,View#getKeepScreenOn 获取是否保持常亮。示例代码如下所示。

textureView.setKeepScreenOn(true); // 设置屏幕保持常亮
boolean isKeepScreenOn = textureView.getKeepScreenOn(); // 是否保持屏幕常亮

监听播放状态

监听播放状态的示例代码如下所示。

ttVideoEngine.setVideoEngineCallback(new VideoEngineCallback() {
    @Override
    public void onPlaybackStateChanged(TTVideoEngine engine, int playbackState) {
        Log.v("VideoPlay", "onPlaybackStateChanged " + engine + " " + playbackState);
        // 播放状态改变回调
        switch (playbackState) {
            case TTVideoEngine.PLAYBACK_STATE_PLAYING:
                // 开始播放
                break;
            case TTVideoEngine.PLAYBACK_STATE_ERROR:
                // 播放出错
                // Note:
                // 1.本消息不代表本次播放失败,内部重试后可能会恢复播放
                // 2.若最终播放失败,会在 VideoEngineListener#onError() 中回调
                break;
            case TTVideoEngine.PLAYBACK_STATE_STOPPED:
                // 播放停止
                break;
            case TTVideoEngine.PLAYBACK_STATE_PAUSED:
                // 播放暂停
                break;
            default:
                break;
        }
    }

    @Override
    public void onLoadStateChanged(TTVideoEngine engine, int loadState) {
        Log.v("VideoPlay", "onLoadStateChanged " + engine + " " + loadState);
        switch (loadState) {
            case TTVideoEngine.LOAD_STATE_PLAYABLE:
                // buffer end or renderStart
                break;
            case TTVideoEngine.LOAD_STATE_STALLED:
                // buffer start
                break;
            case TTVideoEngine.LOAD_STATE_ERROR:
                // load error
                break;
            default:
                break;
        }
    }

    @Override
    public void onVideoSizeChanged(TTVideoEngine engine, int width, int height) {
        Log.v("VideoPlay", "onVideoSizeChanged " + engine + " " + width + " " + height);
        // 播放器解析到视频宽高变化时回调
        // 对于像素比不是 1:1 的视频而言,width/height 不是最终渲染的宽高比
        // 最终渲染的宽高比需要结合 onSARChanged 回调中的 num 和 den 计算
        // displayAspectRatio = (num/(float)den) * (width/(float)height)
    }

    @Override
    public void onBufferingUpdate(TTVideoEngine engine, int percent) {
        Log.v("VideoPlay", "onBufferingUpdate " + engine + " " + percent);
        // 缓存进度回调,可用于展示播放进度条上的二级缓存进度
    }

    @Override
    public void onPrepare(TTVideoEngine engine) {
        Log.v("VideoPlay", "onPrepare " + engine);
        // prepare 时立刻回调
    }

    @Override
    public void onPrepared(TTVideoEngine engine) {
        Log.v("VideoPlay", "onPrepared " + engine + " isSystem " + engine.isSystemPlayer());
        // prepare 完成后回调
    }

    @Override
    public void onRenderStart(TTVideoEngine engine) {
        Log.v("VideoPlay", "onRenderStart " + engine);
        // 开始渲染时回调,可以认为此刻视频画面已经展示
    }

    @Override
    public void onStreamChanged(TTVideoEngine engine, int type) {
        Log.v("VideoPlay", "onStreamChanged " + engine + " " + type);
        // 音视频流变化通知
        // type 枚举:TTVideoEngine.VIDEO_STREAM/TTVideoEngine.AUDIO_STREAM
    }

    @Override
    public void onCompletion(TTVideoEngine engine) {
        Log.v("VideoPlay", "onCompletion " + engine);
        // 播放完成回调
    }

    @Override
    public void onError(Error error) {
        Log.v("VideoPlay", "onError " + error);
        // 播放失败回调
        // 错误码文档:https://www.volcengine.com/docs/4/66441/
        //播放失败后,内部会自动释放播放器
    }

    /**
     * 卡顿开始回调
     *
     * @param code 类型:
     * {@link VideoBufferDetailListener#BUFFERING_TYPE_NET} 网络卡顿
     * {@link VideoBufferDetailListener##BUFFERING_TYPE_DECODER} 解码卡顿
     *
     * @param afterFirstFrame 卡顿发生时机:
     * {@link VideoBufferDetailListener#BEFORE_FIRST_FRAME} 首帧前卡顿
     * {@link VideoBufferDetailListener#AFTER_FIRST_FRAME} 首帧后卡顿
     *
     * @param action 造成卡顿的 action:
     * {@link VideoBufferDetailListener#BUFFER_START_ACTION_NONE} 正常播放过程中卡顿
     * {@link VideoBufferDetailListener#BUFFER_START_ACTION_SEEK} seek
     * {@link VideoBufferDetailListener#BUFFER_START_ACTION_CHANG_RESOLUTION} 切换分辨率
     */
    @Override
    public void onBufferStart(int code, int afterFirstFrame, int action) {
        Log.v("VideoPlay", "onBufferStart " + code + ", " + afterFirstFrame + ", " + action);
        // buffer 开始,展示 loading
    }

    /**
     * 卡顿结束回调
     * @param code
     * {@link VideoBufferDetailListener#BUFFERING_TYPE_NET} 网络卡顿
     * {@link VideoBufferDetailListener##BUFFERING_TYPE_DECODER} 解码卡顿
     */
    @Override
    public void onBufferEnd(int code) {
        Log.v("VideoPlay", "onBufferEnd " + code);
        // buffer 结束,隐藏 loading
    }

    @Override
    public void onSARChanged(int num, int den) {
        Log.v("VideoPlay", "onSARChanged " + num + ", " + den);
        // 视频 sample aspect ratio 回调,用于控制显示模式
        float sampleAspectRatio = num / (float) den;
    }
});

展示当前视频下载速度

点播 SDK 支持回调当前视频一段时间内获取的视频数据大小,可用来在视频的起播、Seek、卡顿等情况下展示当前视频下载速度。实现的步骤和示例代码如下所示。

注意

该功能仅高级版或企业版支持。请确保您已购买高级版或企业版的 License,详见 License 包管理

  1. 在初始化 SDK 前全局开启实时下载速度监听。

    // 全局开启实时下载速度监听,在初始化 SDK 前调用 
    TTVideoEngine.setIntValue(DATALOADER_KEY_INT_NEED_SPEED_TEST_BY_TIMEINTERNAL, 1);
    
    // 初始化 SDK
    Env.init(...);
    
  2. 设置单个实例测速时间间隔。

    // value 为测速时间间隔,单位 MS 推荐值 500 MS
    // 调用时机:设置播放源后调用
    ttVideoEngine.setCustomHeader("X-SpeedTest-TimeInternal", value);
    
  3. 设置回调监听。

    // 设置回调监听
    TTVideoEngine.setDataLoaderListener(new DataLoaderListener());
    
    interface DataLoaderListener {
        ...
        /**
         * 
         * @param what == DATALOADER_KEY_NOTIFY_SPEEDINFO 时为网速回调
         * 此时,code 为 netReadLen(单位 Byte),parameter 为 netReadTime(单位 ms)
         */
        public void  onNotify(int  what, long  code, long  parameter, String info) {
            if  (what == DataLoaderHelper.DATALOADER_KEY_NOTIFY_SPEEDINFO) {
                float  dataSize = code / 1024;
                float  time = parameter / 1000;
                float  speed = dataSize / time; // 单位 KB/s
                Log.d(TAG, "download speed = "  + speed);
            }
        }
    }
    

清除视频缓存

调用 clearAllCaches 方法清除视频缓存:

TTVideoEngine.clearAllCaches(true); // true 清除所有缓存;false LRU 保留最近 10 条缓存。

Seek 到最后一帧

在播放过程中,如果用户拖动进度条将视频快进到视频总时长 3 秒以内的位置,播放器会直接回调播放完成。如果您需要支持 Seek 到最后一帧,可通过如下方法配置:

mVideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_ENABLE_SEEK_LASTFRAME,1);
mVideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_ENABLE_SEEK_END,1);

设置主备域名

SDK 支持 DirectUrl 模式下设置多个域名的 URL 实现主备容灾。

注意

1.35.1 及以上版本支持此功能。

  1. 在初始化前开启 DATALOADER_KEY_INT_ALLOW_TRY_THE_LAST_URL

    TTVideoEngine.setIntValue(DataLoaderHelper.DATALOADER_KEY_INT_ALLOW_TRY_THE_LAST_URL, 1);
    Env.init(/* 省略 */);
    
  2. 构造播放源:

    注意

    主备地址指向的文件必须是同一个文件。

    final String vid = "video id"; // 视频源与 vid 必须一一对应
    final String mainUrl = "http://www.example.com/h264.mp4"; // 主 URL
    final String backUpUrl = "http://www.examplebackup.com/h264.mp4"; // 备 URL
    // cacheKey 建议用 url 中不变的不分的 md5,比如 path 部分的 md5
    final String cacheKey = TTVideoEngine.computeMD5(mainUrl); 
    
    StrategySource directUrlSource = new DirectUrlSource.Builder()
            .setVid(vid)
            .addItem(new DirectUrlSource.UrlItem.Builder()
                    .setUrls(new String[]{mainUrl, backUpUrl})
                    .setCacheKey(cacheKey)
                    .build())
            .build();
    

获取设备 ID

默认情况下,点播 SDK 会自动生成唯一的设备 ID,您可在质量平台追查该设备 ID 的播放记录。参考以下代码获取设备 ID:

// 获取设备 ID
TTVideoEngine.getDeviceID()

说明

如果您自己已有一套独立的用户 ID 体系,希望通过用户 ID 来追查单个用户的单次播放行为,则可自定义设备 ID