全民K歌IOT开放平台
  1. KtvSdk4.0接入说明
全民K歌IOT开放平台
  • 厂商合作流程
  • 快速接入K歌开放平台流程
  • SDK bug提单规范
  • 如何在线调试接口
  • 常见问题FAQ
  • 开放平台API错误码说明
  • 开放平台接入指南
    • 登录鉴权方式介绍V2(推荐)
    • 如何申请接入
    • CDK权限申请流程
    • 暗账号绑定明账号对接文档
    • 全民K歌暗账号登录方案
    • 获取应用级token
      POST
    • 获取登录二维码
      POST
    • 查询二维码的状态
      POST
    • 获取用户级token
      POST
    • 刷新用户级token
      POST
    • 暗账号解绑K歌uid
      POST
    • 获取暗账号绑定信息
      POST
  • KtvSDK3.0接入说明
    • KtvSDK-接入指南
    • KtvSDK-打分
    • KtvSDK-登录
    • KtvSDK-歌词
    • KtvSDK-播放
    • KtvSDK-作品录音、合成与播放2.0版
    • KtvSDK-播放页实现示例
    • KtvSDK-常见问题FAQ
    • KtvSDK-低性能设备处理
    • KtvSDK-反馈
    • KtvSDK-缓存管理
    • KtvSDK-切换音频视频清晰度
    • KtvSDK-跳过前奏
    • Ktv支付-米大师支付
    • KtvSDK-播放失败错误码说明
    • KtvSDK-3.0版本新功能
    • KtvSDK-2.0升级到3.0版本迁移指南
    • KtvSDK-多屏渲染
    • KtvSDK-多屏一起唱---歌词与打分适配
    • KtvSDK-Android4.4设备G2证书兼容处理
    • KtvSDK-APP上报
    • KtvSdk-离线缓存与离线播放
    • KtvSDK-3.0升级到4.0版本迁移指南
  • 基础类
    • tagId对照表
    • 歌曲详情
    • 查询mv的播放地址
    • 搜索
    • 搜索联想
    • 查询歌手歌曲
    • 获取歌曲歌词文件
    • Q音歌曲mid映射K歌
    • 短剧播放链接
    • 查询应用限免配置
    • 转换Q音或酷狗歌曲ID
    • 搜索(海外)
    • 查询歌手类型列表(海外)
    • 查询歌手列表(海外)
    • 查询热门歌手列表(海外)
  • 运营类
    • 排行榜id说明
    • 获取城市id列表
    • 获取专题分类列表
    • 获取云端配置
    • 热门ugc
    • 热搜词
    • 排行榜
    • 获取专题下歌曲信息
    • 获取车联渠道映射
    • 热门推荐
    • (新)排行榜
    • 综合歌单列表查询
    • 设置缓存设备
    • TV版分类歌单列表
    • 获取用户协议
    • 综合歌单列表查询(海外)
    • 榜单列表查询(海外)
    • 自建歌单列表查询(海外)
    • 获取广告配置
    • 拉取短剧合集的列表
    • 获取渠道运营位配置
    • 查询合唱榜单
    • 上报合唱榜单
    • OTA升级版本检查
  • 用户类
    • 用户信息查询
    • 删除用户UGC作品
    • 获取推荐/翻唱作品
    • 获取同城作品
    • 用户作品列表
    • 获取好友作品
    • UGC作品详情
    • 更改作品访问权限
    • 用户个推歌单
  • 支付类
    • 支付接入流程
    • 订单发货使用简述
    • 开通设备会员限免简述
    • 订单发货中通用sign计算规则
    • 通用返回结构
    • 【CDK】CDK兑换
    • 【CDK】生成CDK
    • 【CDK】CDK召回
    • 【CDK】CDK状态查询
    • 【三方支付】订单发货
    • 【三方支付】手机号发货
    • 【三方支付】订单状态查询
    • 【米大师支付】获取会员商品列表
    • 【米大师支付】未登录-查询会员商品列表
    • 【米大师支付】支付下单
    • 【米大师支付】支付成功通知
    • 【米大师支付】查询用户支付成功订单记录
    • 查询设备以及用户是否有赠送资格
    • 查询设备限免剩余时长
    • 设备会员迁移
    • 查询会员赠送时长
    • 同步支付订单接口
    • 【三方支付】存量会员迁移领取查询
    • 【三方支付】存量会员迁移接口
    • 授权设备体验会员
    • 查询用户会员信息
    • 【三方支付】超会发货
    • 取消授权设备体验会员
  • 用户作品-文件类
    • callback_url说明
    • 上传音频源文件
    • (服务端)上传音频作品的链接
  • 通用类
    • 【厂商定制】匹配搜索
    • 获取kg的短链接
    • 三方厂商通用上报
  • KtvSdk4.0接入说明
    • KtvSdk4.0接入文档
    • PK与合唱接入
    • 动效歌词接入文档
    • 手麦接入文档
    • 支持调整录制时长限制
  • 数据模型
    • Schemas
      • protocal.DelUserWorkReq
      • protocal.BarDisplay
      • protocal.GetLyricUrlReq
      • protocal.ConfigItem
      • multipart.FileHeader
      • protocal.ExchangeCdkeyReq
      • protocal.GetLyricUrlRsp
      • protocal.DelUserWorkRsp
      • protocal.GetVipProductRsp
      • protocal.GetChannelReq
      • protocal.ExchangeCdkeyRsp
      • protocal.GetMvUrlReq
      • protocal.GetHitedSongListReq
      • protocal.GoodsInfo
      • protocal.GetChannelRsp
      • protocal.ClientUgcUploadRsp
      • protocal.GetLimitFreeRestTimeReq
      • protocal.GetMvUrlRsp
      • protocal.GetHitedSongListRsp
      • protocal.VipAdItem
      • protocal.GetClassReq
      • protocal.DeleteFileReq
      • protocal.GetLimitFreeRestTimeRsp
      • protocal.GetSongInfoByQmIDReq
      • protocal.GetRecommWorkReq
      • protocal.WebOrderNotifyReq
      • protocal.GetClassRsp
      • protocal.DeleteFileRsp
      • protocal.GetPresentVipConfReq
      • protocal.GetSongInfoByQmIDRsp
      • protocal.GetRecommWorkRsp
      • protocal.WebOrderNotifyRsp
      • protocal.GetConfigReq
      • protocal.UgcUploadReq
      • protocal.GetPresentVipConfRsp
      • protocal.GetSongInfoReq
      • protocal.GetUserInfoReq
      • protocal.GetConfigRsp
      • protocal.UgcUploadRsp
      • protocal.OpenDeviceLimitReq
      • protocal.WebOrderStatusReq
      • protocal.GetSongInfoRsp
      • protocal.GetUserInfoRsp
      • protocal.GetHomePageReq
      • textproto.MIMEHeader
      • protocal.PresentNormalVipReq
      • protocal.WebOrderStatusRsp
      • protocal.GetSongUrlReq
      • protocal.GetUserWorkReq
      • protocal.GetHomePageRsp
      • protocal.PresentNormalVipRsp
      • protocal.GetSongUrlRsp
      • protocal.GetUserWorkRsp
      • protocal.GetHotRecomUgcReq
      • protocal.MvInfo
      • protocal.GetVipInfoRsp
      • protocal.GetHotRecomUgcRsp
      • protocal.SearchReqV2
      • protocal.GetWorkInfoReq
      • protocal.GetHotWordsReq
      • protocal.SearchRsp
      • protocal.GetWorkInfoRsp
      • protocal.GetHotWordsRsp
      • protocal.SingerInfo
      • protocal.SetUgcAccessRightReq
      • protocal.HomePageItem
      • protocal.SingerSongsFilterReq
      • protocal.SetUgcAccessRightRsp
      • protocal.PlayList
      • protocal.SingerSongsFilterRsp
      • protocal.RankSongReq
      • protocal.SingerSongsReq
      • protocal.UgcItem
      • protocal.RankSongRsp
      • protocal.SingerSongsRsp
      • protocal.UserFeatureSongReq
      • protocal.RecommendReq
      • protocal.SongContentStr
      • protocal.UserFeatureSongRsp
      • protocal.RecommendRsp
      • protocal.SongInfo
      • protocal.SongInfoPlus
      • protocal.Theme
      • protocal.TCLGetOpenIdReq
      • protocal.ThemeClass
      • protocal.TCLGetOpenIdRsp
      • protocal.ThemeSongsByIdReq
      • protocal.ThemeSongsByIdRsp
      • protocal.UgcItemInfo
      • protocal.Word
      • protocol.PresentNormalVipReq
      • protocol.UgcUploadRsp
      • protocol.SingerSongsRsp
      • protocol.TCLGetOpenIdRsp
      • protocol.RecommendRsp
      • protocol.Word
      • protocol.UserFeatureSongRsp
      • protocol.CheckDeviceEligibleReq
      • protocol.GetCollectListReq
      • protocol.AddCollectListReq
      • protocol.SongInfo
      • protocol.DelCollectListReq
      • protocol.VipAdItem
      • protocol.WebOrderStatusRsp
      • protocol.ClientUgcUploadReq
      • protocol.GetTopListRsp
      • protocol.OpenDeviceVipRsp
      • protocol.RefundDeviceVipReq
      • protocol.GetLyricUrlRsp
      • protocol.GetSongInfoByQmIDReq
      • protocol.GetSongInfoRsp
      • protocol.GetSongUrlRsp
      • protocol.SearchRsp
      • protocol.SingerSongsFilterReq
      • protocol.SongInfoPlus
      • protocol.GetLyricUrlReq
      • protocol.GetMvUrlReq
      • protocol.GetMvUrlRsp
      • protocol.GetSongInfoByQmIDRsp
      • protocol.GetSongInfoReq
      • protocol.GetSongUrlReq
      • protocol.MvInfo
      • protocol.SearchReqV2
      • protocol.SingerInfo
      • protocol.SingerSongsFilterRsp
      • protocol.SingerSongsReq
      • protocol.SongContentStr
      • protocol.DelCollectListRsp
      • protocol.GetHitedSongListRsp
      • protocol.GetUserWorkReq
      • protocol.GetUserWorkRsp
      • protocol.GetWorkInfoRsp
      • protocol.SetUgcAccessRightRsp
      • protocol.DelUserWorkReq
      • protocol.AddCollectListRsp
      • protocol.DelUserWorkRsp
      • protocol.GetHitedSongListReq
      • protocol.GetCollectListRsp
      • protocol.GetRecommWorkReq
      • protocol.GetUserInfoReq
      • protocol.GetUserInfoRsp
      • protocol.GetRecommWorkRsp
      • protocol.GetVipInfoRsp
      • protocol.GetWorkInfoReq
      • protocol.SetUgcAccessRightReq
      • protocol.UgcItem
      • protocol.UserFeatureSongReq
      • protocol.GoodsInfo
      • protocol.WebOrderNotifyRsp
      • protocol.GetVipProductRsp
      • protocol.BarDisplay
      • protocol.OpenDeviceVipReq
      • protocol.RefundDeviceVipRsp
      • protocol.WebOrderNotifyReq
      • protocol.WebOrderStatusReq
      • protocol.ExchangeCdkeyRsp
      • protocol.GetPresentVipConfRsp
      • protocol.PresentNormalVipRsp
      • protocol.GetLimitFreeRestTimeReq
      • protocol.GetLimitFreeRestTimeRsp
      • protocol.ExchangeCdkeyReq
      • protocol.GetPresentVipConfReq
      • protocol.OpenDeviceLimitReq
      • protocol.GetClassRsp
      • protocol.GetHomePageReq
      • protocol.GetHotRecomUgcReq
      • protocol.GetHotRecomUgcRsp
      • protocol.GetTopListReq
      • protocol.RankSongRsp
      • protocol.RecommendReq
      • protocol.ThemeClass
      • protocol.ThemeSongsByIdRsp
      • protocol.ConfigItem
      • protocol.GetChannelReq
      • protocol.GetChannelRsp
      • protocol.GetClassReq
      • protocol.GetConfigReq
      • protocol.GetConfigRsp
      • protocol.GetHomePageRsp
      • protocol.GetHotWordsReq
      • protocol.GetHotWordsRsp
      • protocol.HomePageItem
      • protocol.PlayList
      • protocol.RankSongReq
      • protocol.Theme
      • protocol.ThemeSongsByIdReq
      • protocol.UgcItemInfo
      • protocol.GetShortUrlReq
      • protocol.GetShortUrlRsp
      • protocol.SearchHintRsp
      • protocol.SearchHintReq
      • protocol.MultiPlaylistRsp
      • protocol.PlaylistInfo
      • protocol.MultiPlaylistReq
      • protocol.GetPayOrderListReq
      • protocol.GetPayOrderListRs p
      • protocol.GetUserVipInfoReq
      • protocol.GetUserVipInfoRsp
      • protocal.GetTvPlaylistSetReq
      • protocol.Playlist
      • protocol.GetTvPlaylistSetRsp
      • protocol.TvChorusUser
      • protocol.ClientUgcUploadRsp
      • protocol.Episode
      • protocol.UgcUploadReq
      • protocol.GenerateNewOrderCdkReq
      • protocol.GetMiniShowUrlReq
      • protocol.GenerateNewOrderCdkRsp
      • protocol.DeleteFileReq
      • protocol.GetVipProductReq
      • protocol.GetMiniShowUrlRsp
      • protocol.DeleteFileRsp
      • protocol.MergerQrCode
      • protocol.GetSdkTokenReq
      • protocol.GetSdkTokenRsp
      • protocol.PlacePayOrderReq
      • protocol.PlacePayOrderRsp
      • protocol.QueryReceiveTransferVipReq
      • protocol.QueryReceiveTransferVipRsp
      • protocol.ReceiveTransferVipReq
      • protocol.ReceiveTransferVipRsp
      • protocol.RecallNewCDKReq
      • protocol.QueryNewCDKReq
      • protocol.QueryNewCDKRsp
      • protocol.OpenDeviceLimitRsp
      • protocol.RecallNewCDKRsp
      • protocol.WebThirdpartyOrderReq
      • protocol.GetMiniShowCollectionReq
      • protocol.WebThirdpartyOrderRsp
      • protocol.GetMiniShowCollectionRsp
      • protocol.MiniShowItem
      • protocol.TCLGetOpenIdReq
  1. KtvSdk4.0接入说明

支持调整录制时长限制

功能介绍#

sdk4.0取消了必须唱完一整首才能获取打分的限制,支持在唱到一半(满足一定满足条件时)获取打分结果、录音文件的信息。

中途获取打分需要满足的条件#

时长要求:正式唱歌20s以上(不含歌曲的前奏时长)或唱完整首
打分要求:
五维打分:有效句子数>=5(针对每句的分数必须的分数值)
普通打分:无特殊要求
如何查看是否满足获取打分/录音文件条件?
在播放结束时,播放器会根据KaraokePlayer.hasReachSaveRecordFileThreshold()==true确定已满足录制条件。
日志中关键字:hasReachSaveRecordFileThreshold

唱歌中途获取打分分数流程#

示例场景:在切歌时判断是否弹出打分弹窗。如果还不能获取分数,则播放一下首。
处理流程如下:
1.
先调用player.pause()暂停播放
2.
判断是否满足获取打分条件
满足条件:获取打分,将打分结果保存到TotalScoreResultInfo中供后续使用
显示打分弹窗
如果需要获取录音文件,调用player.stop()触发播放停止,以便可以后续在onPlayEnd()中可以接收到录音文件地址
不满足条件:跳过,直接做下一个操作(例如播放下一首)
核心代码如下:

定制录制时长示例代码#

因为这一块的逻辑跟UI交互有关,一般会跟业务逻辑结合,而且场景也比较多,所以下面也附上打分模块的实现示例。
代示例代码展示了如下场景的处理逻辑:
class TotalScoreResultInfo {
    companion object {
        /** 唱到一半获取打分:普通打分场景 */
        const val SCENE_ON_SCORE_FINISH: String = "onScoreFinish"
        /** 唱到一半获取打分:五维打分完成场景 */
        const val SCENE_ON_MULTI_SCORE_FINISH: String = "onMultiScoreFinish"
        /** 播放下一首时判断是否要弹出打分弹窗 */
        const val SCENE_PLAY_NEXT: String = "playNext"
        /** 退出播放时获取打分 */
        const val SCENE_EXIT_PAGE: String = "exitPage"
    }
...
}
checkShowScoreDialog()是触发入口。
class DemoScoreModule(private val viewModel: IPlayBaseInfoVM, containerView: ViewGroup) :
    Scene(containerView), ScoreDialog.OnClickListener, BaseScoreDialog.OnClickListener{
    override val TAG = "DemoScore"

    /** 打分辅助类 */
    private var mIntonationDelegate: SingIntonationDelegate? = null


    private var mScoreDialog: ScoreDialog? = null

    private var multiScoreDialog:MultiScoreDialog?=null

    private var containerView: ViewGroup? = null
    private var intonationViewer: AbstractIntonationView? = null
    private var pkScoreBar: SimpleScoreBar? = null
    private var pkScoreBarNew:PkScoreBar?=null
    private var perfectAnimView: PerfectAnimView? = null

    private var ktvScoreCallBackImpl:KtvScoreCallbackImpl?=null
    private var scoreListenerImpl: ScoreListenerImpl? = null
    private val mHandler = Handler(Looper.getMainLooper())
    private var isScoreDialogShown = false
    private var isEnterEdit =  false
    private var isPlayEnd =  false

    private val multiScoreLogBuilder = StringBuilder()

    private var mSingCompetitor: SingCompetitor? = null
    private var multiScoreResultInfo=MultiScoreResultInfo()

    override val owner = containerView.context as LifecycleOwner

    /**
     * 初始化视图组件
     * 包括音准视图、打分条、完美动画视图等
     */
    override fun initView() {
        setRootView(R.layout.play_scene_score)

        val userService = KServiceManager.getService(IKUser::class.java)
        mRootView?.let {
            containerView = it.findViewById(R.id.intonation_container)
            intonationViewer = it.findViewById(R.id.player_float_intonation_viewer)
            pkScoreBar = it.findViewById(R.id.pk_score_bar)
            pkScoreBarNew=it.findViewById(R.id.pkScoreBar)
            perfectAnimView = it.findViewById(R.id.karaoke_perfect_view)

            mIntonationDelegate =
                SingIntonationDelegate(
                    containerView,
                    intonationViewer,
                    pkScoreBar,
                    perfectAnimView,
                )
        }
    }

    /**
     * 更新打分条的可见性
     * 控制普通打分条和新版PK打分条的显示状态
     */
    private fun updateScoreBarVisibility(){
        pkScoreBar?.visibility = View.VISIBLE
        pkScoreBarNew?.visibility = View.GONE
    }

    private fun log(msg:String) {
        Log.i(TAG, msg)
    }

    /**
     * 初始化监听器
     * 监听歌曲信息变化、打分开关、播放下一首等事件
     */
    override fun initListener() {
        // 监听当前歌曲信息变化
        viewModel.currSongInfo().observe(owner) {
            log("currSongInfo ${it?.mid}")

            if (ktvScoreCallBackImpl==null){
                ktvScoreCallBackImpl = KtvScoreCallbackImpl()
            }

            if (scoreListenerImpl==null){
                scoreListenerImpl = ScoreListenerImpl()
            }

            //KtvPlayerImpl中可能会将所有的回调都清除掉,所以这里重新添加一下
            viewModel.currPlayer()?.apply {
                removeOnScoreListener(scoreListenerImpl)
                removeCallback(ktvScoreCallBackImpl)
                addOnScoreListener(scoreListenerImpl)
                addCallback(ktvScoreCallBackImpl)
            }
        }

        //听歌/K歌切换时,更新打分条的可见性
        PlayConsole.get().songType.observe(owner) {
            updateScoreViewVisibility()
        }

        //打开/关闭打分时,更新打分条的可见性
        PlayConsole.get().openScore.observe(owner) {
            updateScoreViewVisibility()
            if (it) {
                //开启打分
                viewModel.currSongInfo().value?.apply {
                    val isKSong = songType == SongType.SONG_TYPE_K_SONG
                    PlayConsole.get().setShowPk(isKSong && isSupportPk)
                }
            }else {
                PlayConsole.get().setShowPk(false)
                PlayConsole.get().setShowChorus(false)
            }
        }

        //播放下一首时,检查是否需要显示打分结果弹框
        PlayConsole.get().showScoreOrPlayNext.value = null
        PlayConsole.get().showScoreOrPlayNext.observe(owner) {
            if(it.isNullOrEmpty()) {
                return@observe
            }
            checkShowScoreDialog(TotalScoreResultInfo.SCENE_PLAY_NEXT)
        }

        //记录当前打分结果。PlayConsole类只是示例,厂商可用类似方式自行保存此结果
        //val scoreResultInfo = MutableLiveData<TotalScoreResultInfo?>()
        PlayConsole.get().scoreResultInfo.postValue(null)
    }

    /**
     * 获取手动触发的打分结果信息
     * @param scene 触发场景(播放下一首、退出页面等)
     * @return 打分结果信息,如果没有打分数据则返回null
     */
    private fun getManualTriggerResultInfo(scene: String?): TotalScoreResultInfo? {
        //mSingCompetitor 从onPrepareScore获取
        val singCompetitor = mSingCompetitor ?: return null
        val scoreResultInfo = TotalScoreResultInfo()
        scoreResultInfo.scoreScene  = scene
        scoreResultInfo.isMultiScore = singCompetitor.isMultiScorer
        if(!scoreResultInfo.isMultiScore) {
            scoreResultInfo.totalScore = singCompetitor.totalScore
            scoreResultInfo.scoreLevelString = singCompetitor.scoreLevelString
            scoreResultInfo.scoreLevel =
                ScoreUtil.scoreLevelStringToInt(scoreResultInfo.scoreLevelString)
        } else {
            scoreResultInfo.multiScoreResult = singCompetitor.multiScoreResult
        }
        return scoreResultInfo
    }

    /**
     * 更新打分视图的可见性
     * 根据歌曲类型、打分开关、编辑模式等条件决定是否显示打分相关视图
     */
    private fun updateScoreViewVisibility() {
        val isShow = isShowScore()
        log(
            "updateScoreViewVisibility  isShow: $isShow, isEnableScore: ${KtvPlayerConfig.getInstance().isEnableScore}" +
                    ", songType: ${PlayConsole.get().songType.value}" +
                    ", openScore: ${PlayConsole.get().openScore.value}" +
                    ", editMode: ${PlayConsole.get().editMode.value}"
        )
        if (isShow) {
            containerView?.post {
                containerView?.visibility = View.VISIBLE
                intonationViewer?.visibility = View.VISIBLE
                perfectAnimView?.visibility = View.VISIBLE
                updateScoreBarVisibility()
            }
        } else {
            if(viewModel.currSongInfo().value != null) {
                log("not support score")
            }
            containerView?.post {
                containerView?.visibility = View.GONE
                intonationViewer?.visibility = View.GONE
                pkScoreBar?.visibility = View.GONE
                pkScoreBarNew?.visibility=View.GONE
                perfectAnimView?.visibility = View.GONE
            }
        }
    }
    
    /**
     * 判断是否应该显示打分功能。这里的判断方式仅供参考,厂商可根据自身需求进行判断
     * @return true表示应该显示打分,false表示不显示
     */
    private fun isShowScore(): Boolean {
        return PlayConsole.get().songType.value == SongType.SONG_TYPE_K_SONG
                && PlayConsole.get().openScore.value != false
                && PlayConsole.get().editMode.value != true
                && viewModel.currSongInfo().value?.mNoteBytes != null
                && KtvPlayerConfig.getInstance().isEnableScore
                && !KtvPlayerConfig.getInstance().isCloseScoreUI
    }

    /**
     * 打分监听器实现类
     * 处理打分准备、打分进行中、打分完成等回调
     */
    inner class ScoreListenerImpl : KaraokePlayer.OnScoreListener() {

        override fun onPrepareScore(competitor: SingCompetitor?, nums: Int) {
            Logger.d(TAG, "onPrepareScore")
            mSingCompetitor = competitor
            if (KtvPlayerConfig.getInstance().isCloseScoreUI) {
                return
            }
            setScoreDialogShown(false, null)
        }

        override fun onDoingScore(score: Int, level: String?, timeStamp: Int,sentenceIndex:Int) {
            refreshScore(score, timeStamp.toLong(), null,sentenceIndex)
        }

        override fun onDoingMultiScore(
            lastScore: Int,
            timeStamp: Long,
            lastLongToneScore: Int,
            lastRhythmScore: Int,
            lastStableScore: Int,
            dynamicScore: Int,
            skillScore: Int,
            lastSkillTrillCount: Int,
            lastSkillGlintCount: Int,
            lastSkillTransliterationCount: Int,
            lyricIndex:Int
        ) {
            if (KtvPlayerConfig.getInstance().isCloseScoreUI) {
                return
            }
            multiScoreResultInfo.resetAll()
            multiScoreResultInfo.lastLongToneScore = lastLongToneScore
            multiScoreResultInfo.lastRhythmScore = lastRhythmScore
            multiScoreResultInfo.lastStableScore = lastStableScore
            multiScoreResultInfo.lastDynamicScore = dynamicScore
            multiScoreResultInfo.lastSkillScore = skillScore
            multiScoreResultInfo.lastSkillTrillCount = lastSkillTrillCount
            multiScoreResultInfo.lastSkillTransliterationCount = lastSkillGlintCount
            multiScoreLogBuilder.append("lastLongToneScore: ").append(lastLongToneScore)
                .append("lastScore: ").append(lastScore)
                .append(" lastRhythmScore: ").append(lastRhythmScore)
                .append(" lastStableScore: ").append(lastStableScore)
                .append(" dynamicScore: ").append(dynamicScore)
                .append(" skillScore: ").append(skillScore)
                .append(" lastSkillTrillCount: ").append(lastSkillTrillCount)
                .append(" lastSkillGlintCount: ").append(lastSkillGlintCount)
            Logger.i(TAG, "sentence score info: $multiScoreLogBuilder")
            multiScoreLogBuilder.setLength(0)
            refreshScore(lastScore, timeStamp, multiScoreResultInfo,lyricIndex)
        }

        /**
         * 五维打分完成的回调
         * @param lastSentenceIndex 最后一句索引
         * @param totalSentences 总句数
         */
        override fun  onMultiScoreFinish(
            finalLongToneScore: Int,
            finalRhythmScore: Int,
            finalStableScore: Int,
            finalDynamicScore: Int,
            finalSkillScore: Int,
            lastSentenceIndex: Int,
            totalSentences: Int
        ) {

            val multiScoreResult = MultiScoreResultInfo()
            multiScoreResult.totalLongToneScore = finalLongToneScore
            multiScoreResult.totalRhythmScore = finalRhythmScore
            multiScoreResult.totalStableScore = finalStableScore
            multiScoreResult.totalDynamicScore = finalDynamicScore
            multiScoreResult.totalSkillScore = finalSkillScore
            multiScoreResult.lastSentenceIndex = lastSentenceIndex
            multiScoreResult.totalSentences = totalSentences

            val scoreResult = TotalScoreResultInfo()
            scoreResult.scoreScene = TotalScoreResultInfo.SCENE_ON_MULTI_SCORE_FINISH
            scoreResult.isMultiScore = true
            scoreResult.multiScoreResult = multiScoreResult
            Logger.i(TAG, "onMultiScoreFinish result: $scoreResult")

            checkShowMultiScoreDialog(scoreResult)
        }

        /**
         * 普通打分完成的回调
         * @param totalScore 总分
         * @param level 评级
         */
        override fun onFinishScore(totalScore: Int, level: String?) {
            Logger.d(TAG, "onFinishScore totalScore $totalScore")

            mSingCompetitor?.let {
                val scoreResult = TotalScoreResultInfo()
                scoreResult.scoreScene = TotalScoreResultInfo.SCENE_ON_SCORE_FINISH
                scoreResult.totalScore = totalScore
                scoreResult.scoreLevelString = level ?: ""
                scoreResult.scoreLevel = ScoreUtil.scoreLevelStringToInt(level)
                checkShowNormalScoreDialog(scoreResult)
            }
        }
    }

    /**
     * 刷新打分显示
     * @param lastSentenceScore 上一句分数
     * @param timeStamp 时间戳
     * @param multiScoreResultInfo 五维打分信息
     * @param sentenceIndex 句子索引
     */
    private fun refreshScore(lastSentenceScore: Int, timeStamp: Long, multiScoreResultInfo: MultiScoreResultInfo?,sentenceIndex: Int) {
        if (lastSentenceScore > 70) {
            SingCompetition.get().isLastScorePositive = true
        }
        if (lastSentenceScore != -1 && lastSentenceScore != 0) {
            postUpdateScore(lastSentenceScore, timeStamp.toFloat(), multiScoreResultInfo,sentenceIndex)
        }
    }

    /**
     * 设置打分弹窗的显示状态
     * @param isShown 是否显示
     * @param resultInfo 打分结果信息
     */
    private fun setScoreDialogShown(isShown: Boolean, resultInfo: TotalScoreResultInfo?) {
        Logger.i(TAG, "setScoreDialogShown $isShown")
        isScoreDialogShown = isShown
        PlayConsole.get().isScoreDialogShown.postValue(isShown)
        if(resultInfo != PlayConsole.get().scoreResultInfo.value) {
            PlayConsole.get().scoreResultInfo.postValue(resultInfo)
        }
    }

    /**
     * 发送更新分数的消息
     * @param score 分数
     * @param timeStamp 时间戳
     * @param multiScoreInfo 五维打分信息
     * @param sentenceIndex 句子索引
     */
    private fun postUpdateScore(score: Int, timeStamp: Float, multiScoreInfo: MultiScoreResultInfo?,sentenceIndex: Int) {
        mIntonationDelegate?.postUpdateScore(score,timeStamp,multiScoreInfo,sentenceIndex)
    }

    /**
     * KTV播放器回调实现类
     * 处理播放开始、暂停、恢复、播放结束等事件
     */
    inner class KtvScoreCallbackImpl : KaraokePlayerListener() {
        /**
         * 歌词资源变化的回调
         * 检查歌曲是否支持打分,并启动MIDI视图
         * @param request 播放请求对象
         */
        override fun onLyricResourceChanged(request: KaraokePlayRequest) {
            super.onLyricResourceChanged(request)
            if (request.songInfoObject.mNoteBytes == null && request.songInfoObject.songType == SongType.SONG_TYPE_K_SONG) {
                Logger.d(TAG, "current song has no noteBytes, close score")
                if (KtvPlayerConfig.getInstance().isEnableScore && PlayConsole.get().openScore.value == true) {
                    PlayConsole.get().setScoreOpen(false)
                    mIntonationDelegate?.stopMidi()
                    KGToast.show("该歌曲不支持打分,已为您关闭打分")
                }
            }
            startMidiView()
        }

        /**
         * 播放位置变化的回调
         * 同步MIDI视图的播放进度
         * @param mKtvPlayRequest 播放请求
         * @param currentTime 当前播放时间
         * @param duration 总时长
         */
        override fun onPlayPositionChange(
            mKtvPlayRequest: KaraokePlayRequest,
            currentTime: Long,
            duration: Long
        ) {
            if (KtvPlayerConfig.getInstance().isCloseScoreUI) {
                return
            }
//            Logger.d(TAG, "onPlayPositionChange:currentTime=$currentTime,duration=$duration")
            getActivity()?.let {
                if (!KGDialog.isActivityDestroyed(it)) {
                    mIntonationDelegate?.seekMidi(currentTime.toLong())
                }
            }
        }

        /**
         * 播放恢复的回调
         * 重新启动MIDI视图
         * @param mKtvPlayRequest 播放请求对象
         */
        override fun onResume(mKtvPlayRequest: KaraokePlayRequest) {
            Logger.d(TAG, "call onResume")
            startMidiView()
        }

        /**
         * 启动MIDI视图
         * 开始显示音准线和打分相关视图
         */
        private fun startMidiView() {
            if (KtvPlayerConfig.getInstance().isCloseScoreUI) {
                return
            }
            if (KtvPlayerConfig.getInstance().isEnableScore) {
                log("startMidiView")
                mIntonationDelegate?.startMidi(viewModel.currPlayer()?.timeLineTime ?: 0)
            }
            updateScoreViewVisibility()
        }

        /**
         * 播放暂停的回调
         * 停止MIDI视图
         * @param mKtvPlayRequest 播放请求对象
         */
        override fun onPause(mKtvPlayRequest: KaraokePlayRequest) {
            Logger.d(TAG, "onPause")
            if (KtvPlayerConfig.getInstance().isCloseScoreUI) {
                return
            }
            mIntonationDelegate?.stopMidi()
        }

        /**
         * 缓冲开始的回调
         * @param mKtvPlayRequest 播放请求对象
         */
        override fun onBufferingStart(mKtvPlayRequest: KaraokePlayRequest) {
            Logger.d(TAG, "onBufferingStart.")
            mIntonationDelegate?.onBuffingStart()
        }

        /**
         * 缓冲结束的回调
         * @param mKtvPlayRequest 播放请求对象
         */
        override fun onBufferingEnd(mKtvPlayRequest: KaraokePlayRequest) {
            Logger.d(TAG, "onBufferingEnd.")
            mIntonationDelegate?.onBufferingEnd()
        }

        /**
         * 播放结束的回调
         * 处理打分弹窗显示和播放下一首逻辑
         * @param request 播放请求对象
         * @param endInfo 播放结束信息
         */
        override fun onPlayEnd(request: KaraokePlayRequest, endInfo: PlayEndInfo) {
            Logger.d(
                TAG,
                "onPlayEnd " + multiScoreDialog?.isShowing + "  " + isScoreDialogShown + " isEnd " + endInfo.isPlayFinish + " isSegmentPlay " + request.songInfoObject.isSegmentPlay
            )
            isPlayEnd = true
            if (endInfo.isPlayFinish && !isShowScore()) {
                playNextIfNeed()
            }
            if (multiScoreDialog?.isShowing != true && !isScoreDialogShown) {
                //五维打分结果还没有回调,最多等待3s
                mHandler.postDelayed({
                    if (multiScoreDialog?.isShowing == false) {
                        onPlayEndInternal(endInfo)
                    }
                }, 3000L)
            } else {
                onPlayEndInternal(endInfo)
            }
        }

        /**
         * 进度跳转完成的回调
         * 同步MIDI视图到指定位置
         * @param request 播放请求对象
         * @param position 跳转位置
         */
        override fun onSeekComplete(request: KaraokePlayRequest, position: Long) {
            Logger.d(TAG, "onSeekComplete currentTime $position")
            if (KtvPlayerConfig.getInstance().isCloseScoreUI) {
                return
            }
            mIntonationDelegate?.seekMidi(position)
        }
    }

    /**
     * 播放结束内部处理
     * 处理播放结束后的逻辑,包括播放下一首和进入编辑模式
     * @param endInfo 播放结束信息
     */
    private fun onPlayEndInternal(endInfo: PlayEndInfo){
        mHandler.post {
//            mIntonationDelegate?.onSongPlayComplete()
//            mIntonationDelegate?.stopMidi()
//            mIntonationDelegate?.releaseMidi()

            val openScore =
                viewModel.currSongInfo().value?.mNoteBytes != null && viewModel.currSongInfo().value?.songType == SongType.SONG_TYPE_K_SONG

            if (endInfo.isPlayFinish && (!openScore || mScoreDialog?.isShowing == false)) {
                playNextIfNeed()
            }

            //如果是由finishScore触发的打分弹窗,那么就显示编辑页面
            if (isEnterEdit) {
                viewModel.currEditSongInfo().value = viewModel.currSongInfo().value
                PlayConsole.get().editMode.value = true
            }
        }
    }
    
    /**
     * 点击打分弹窗的左侧按钮的回调(保存发布)
     * 停止播放并进入编辑模式
     * @param dialog 弹窗对象
     */
    override fun onClickLeftBtn(dialog: DialogInterface?) {
        dialog?.dismiss()
        isEnterEdit = true
        if (isPlayEnd) {
            viewModel.currEditSongInfo().value = viewModel.currSongInfo().value
            PlayConsole.get().editMode.value = true
        }
        viewModel.currPlayer()?.stop()
        PopupManager.get().dismiss(PlayConsolePopupView::class.java.name, null)
    }

    /**
     * 点击打分弹窗的右侧按钮的回调(下一首)
     * 播放下一首歌曲
     * @param dialog 弹窗对象
     */
    override fun onClickRightBtn(dialog: DialogInterface?) {
        playNextIfNeed()
        dialog?.dismiss()
    }

    /**
     * 点击下一首按钮的回调
     * 播放下一首并关闭打分弹窗
     */
    override fun onClickNextSong() {
        playNextIfNeed()
        dismissScoreDialog()
    }

    /**
     * 检查是否显示打分弹窗
     * @param scene 触发场景
     * @return true表示显示了弹窗,false表示未显示
     */
    private fun checkShowScoreDialog(scene: String?): Boolean {
        if (!shouldShowScoreDialog()) {
            if(Objects.equals(scene, TotalScoreResultInfo.SCENE_PLAY_NEXT)) {
                playNextIfNeed()
            }
            return false
        }
        viewModel.currPlayer()?.pause()

        val scoreResultInfo = getManualTriggerResultInfo(scene)
        if(scoreResultInfo == null) {
            if(Objects.equals(scene, TotalScoreResultInfo.SCENE_PLAY_NEXT)) {
                playNextIfNeed()
            }
            return false
        }

        if (scoreResultInfo.isMultiScore) {
            return checkShowMultiScoreDialog(scoreResultInfo)
        } else {
            return checkShowNormalScoreDialog(scoreResultInfo)
        }
    }

    /**
     * 检查并显示普通打分弹窗(单维度打分)
     * @param totalScoreResult 总打分结果信息
     * @return true表示显示了弹窗,false表示未显示
     */
    private fun checkShowNormalScoreDialog(totalScoreResult: TotalScoreResultInfo?): Boolean {
        PlayConsole.get().scoreResultInfo.postValue(totalScoreResult)
        val scene = totalScoreResult?.scoreScene ?: ""
        if(totalScoreResult == null) {
            if(!Objects.equals(scene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                playNextIfNeed()
            }
            return false
        }

        if (!isAlive() || totalScoreResult.totalScore <= 0) {
            if(!Objects.equals(scene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                playNextIfNeed()
            }
            return false
        }

        Logger.i(TAG, "checkShowNormalScoreDialog result: $totalScoreResult")

        containerView?.post {
            PopupManager.get().forceShow(ScorePopupView::class.java.name)
            val dialog = ScoreDialog(
                getActivity(),
                ScoreDialogInfo(
                    viewModel.currSongInfo().value?.songName ?: "",
                    totalScoreResult.totalScore,
                    totalScoreResult.scoreLevel,
                    0,
                    true,
                    PlayerManager.playList.size() > 0
                )
            )
//            if (viewModel.getPkChorus()?.isChorus()==true){
//                Logger.i(TAG,"dialog.setChorusInfo chorusRank=${chorusRank}")
//                if (chorusRank!=-1){
//                    dialog.setChorusInfo(chorusRank,pkChorusIniator,pkChorusChallenger)
//                }
//            }else if(viewModel.getPkChorus()?.isPk()==true){
//                Logger.i(TAG,"dialog.setPkInfo UserType=${pkChorusIniator.getUserType()}")
//                if (pkChorusIniator.getUserType()!= PkAndChorusUserType.UNKNOWN){
//                    dialog.setPkInfo(pkChorusIniator,pkChorusChallenger)
//                }
//            }
            dialog.setOnClickListener(this@DemoScoreModule)
            dialog.show()
            mScoreDialog = dialog

            setScoreDialogShown(true, totalScoreResult)
        }
        return true
    }

    /**
     * 显示五维打分弹窗
     * scoreResult是总分数信息,multiScoreResultInfo是最新分数信息,先不合并
     */
    private fun checkShowMultiScoreDialog(totalScoreResult: TotalScoreResultInfo?): Boolean {
        val scene = totalScoreResult?.scoreScene ?: ""
        if(totalScoreResult == null) {
            if(!Objects.equals(scene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                playNextIfNeed()
            }
            return false
        }

        val scoreResult = totalScoreResult.multiScoreResult
        val cause= totalScoreResult.scoreScene
        if (scoreResult == null) {
            if(!Objects.equals(scene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                playNextIfNeed()
            }
            return false
        }
        Logger.i(TAG, "checkShowMultiScoreDialog cause: $cause, result: $scoreResult")

        if (!isAlive() || this.multiScoreResultInfo == null || KtvPlayerConfig.getInstance().isCloseScoreUI) {
            Logger.d(
                TAG,
                "setMultiScoreData fail bcs isNotAlive: " + (this.multiScoreResultInfo == null)
            )
            if(!Objects.equals(scene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                playNextIfNeed()
            }
            return false
        }

        if (scoreResult.lastSentenceIndex < 0 || scoreResult.totalSentences <= 0
        ) {
            if(!Objects.equals(scene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                playNextIfNeed()
            }
            return false
        }

        val arrayList: ArrayList<RadarScore> = ArrayList<RadarScore>()
        val adjust = MultiScoreAdjustor()
        adjust.totalStableScore = scoreResult.totalStableScore
        adjust.totalRhythmScore = scoreResult.totalRhythmScore
        adjust.totalSkillScore = scoreResult.totalSkillScore
        adjust.totalLongToneScore = scoreResult.totalLongToneScore
        adjust.totalDynamicScore = scoreResult.totalDynamicScore

        //技巧这个维度的分数并不好打 或者打的偏低 这里做个兜底策略
        val originTotalSkillScore: Int = adjust.totalSkillScore
        //originTotalSkillScore只会>=0
        if (originTotalSkillScore >= 0) {
            //其他四个维度的总分的平均值
            //如果技巧的总分低于其他四个维度的总分的平均值 那么需要对技巧分进行加权
            val totalMultiScoreExpSkill: Int = adjust.getTotalMultiScoreExpSkill()
            if (totalMultiScoreExpSkill - originTotalSkillScore > 0) {
                val random = Random()
                var skillScoreAfterWeight = random.nextInt(10) + totalMultiScoreExpSkill
                if (skillScoreAfterWeight > 100) skillScoreAfterWeight = 100
                adjust.totalSkillScore = skillScoreAfterWeight
                scoreResult.totalSkillScore = skillScoreAfterWeight
                Logger.i(TAG, "originTotalSkillScore: $originTotalSkillScore skillScoreAfterWeight: $skillScoreAfterWeight")
            }
        }
        arrayList.add(RadarScore(adjust.totalStableScore, "音准"))
        arrayList.add(RadarScore(adjust.totalRhythmScore, "节奏"))
        arrayList.add(RadarScore(adjust.totalSkillScore, "技巧"))
        arrayList.add(RadarScore(adjust.totalLongToneScore, "气息"))
        arrayList.add(RadarScore(adjust.totalDynamicScore, "情感"))
        val totalScore = mIntonationDelegate?.getTotalScore() ?: 0
        if (totalScore <= 0) {
            if(!Objects.equals(scene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                playNextIfNeed()
            }
            return false
        }
        totalScoreResult.totalScore = totalScore
        val singSentences = Math.min(scoreResult.lastSentenceIndex + 1, scoreResult.totalSentences)
        totalScoreResult.scoreLevel= ScoreUtil.getRankIndex(totalScore, singSentences)
        totalScoreResult.scoreLevelString = ""

        containerView?.post{
            val scoreDialog = MultiScoreDialog(getActivity())
            multiScoreDialog=scoreDialog
            scoreDialog.setTotalMultiScore(adjust.getTotalMultiScore())
//            if (viewModel.getPkChorus()?.isChorus()==true){
//                Logger.i(TAG,"multi scoreDialog.setChorusInfo chorusRank=${chorusRank}")
//                if (chorusRank!=-1){
//                    scoreDialog.setChorusInfo(chorusRank,pkChorusIniator,pkChorusChallenger)
//                }
//            }else if(viewModel.getPkChorus()?.isPk()==true){
//                Logger.i(TAG,"multi scoreDialog.setPkInfo UserType=${pkChorusIniator.getUserType()}")
//                if (pkChorusIniator.getUserType()!= PkAndChorusUserType.UNKNOWN){
//                    scoreDialog.setPkInfo(pkChorusIniator,pkChorusChallenger)
//                }
//            }
            scoreDialog.songName(viewModel.currSongInfo().value?.songName ?: "")
            scoreDialog.scoreValue(totalScoreResult.totalScore)
            scoreDialog.scoreLevel(totalScoreResult.scoreLevel)
            scoreDialog.setOnClickListener(this@DemoScoreModule)
            scoreDialog.setLeftBtnName("保存发布")
            scoreDialog.setRightBtnName("下一首")
            scoreDialog.setData(arrayList)
            scoreDialog.show()
            scoreDialog.refreshMultiScoreUI()

            setScoreDialogShown(true, totalScoreResult)
        }
        return true
    }

    /**
     * 判断是否应该显示打分弹窗
     * @return true表示应该显示,false表示不应该显示
     */
    private fun shouldShowScoreDialog(): Boolean {
        if(isScoreDialogShown || !isShowScore()) {
            return false
        }

        // 如果没有达到保存录音文件阈值,不显示打分弹窗
        if(viewModel.currPlayer()?.hasReachSaveRecordFileThreshold() != true) {
            return false
        }
        return true
    }

    /**
     * 返回键事件回调
     * @param dialog 弹窗对象
     */
    override fun onKeyBackEvent(dialog: DialogInterface?) {
        onKeyBackInternal(dialog, "onKeyBackEvent")
    }

    /**
     * 点击保存并发布按钮的回调
     * 进入编辑模式并停止播放
     */
    override fun onClickSaveAndPush() {
        Logger.d(TAG, "onClickSaveAndPush")
        val scoreScene = PlayConsole.get().scoreResultInfo.value?.scoreScene
        dismissScoreDialog()

        isEnterEdit = true
        viewModel.currEditSongInfo().value = viewModel.currSongInfo().value
        PlayConsole.get().editMode.value = true
        //获取分数结果后就可以停止播放器了
        if(Objects.equals(scoreScene, TotalScoreResultInfo.SCENE_PLAY_NEXT)
            || Objects.equals(scoreScene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
            viewModel.currPlayer()?.stop()
        }
    }

    /**
     * 倒计时结束的回调
     * 自动播放下一首
     * @param totalCountDownTime 总倒计时时间
     */
    override fun onCountDownFinish(totalCountDownTime: Int) {
        Logger.d(TAG, "onCountDownFinish")
        playNextIfNeed()
    }

    /**
     * 页面销毁的回调
     * 清理资源和监听器
     */
    override fun onDismiss() {
        mHandler.removeCallbacksAndMessages(null)
        viewModel.currPlayer()?.removeOnScoreListener(scoreListenerImpl)
        viewModel.currPlayer()?.removeCallback(ktvScoreCallBackImpl)

        mIntonationDelegate?.stopMidi()
        mIntonationDelegate?.releaseMidi()
        dismissScoreDialog()
        super.onDismiss()
    }

    /**
     * 分发按键事件
     * 处理返回键事件,在适当时机显示打分弹窗
     * @param keyEvent 按键事件
     * @return true表示消费了事件,false表示未消费
     */
    override fun dispatchKeyEvent(keyEvent: KeyEvent): Boolean {
        Logger.i(TAG, "dispatchKeyEvent $keyEvent")
        if(keyEvent.action == KeyEvent.ACTION_DOWN && keyEvent.keyCode == KeyEvent.KEYCODE_BACK) {
            if(checkShowScoreDialog(TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
                return true
            }
        }
        return false
    }

    /**
     * 关闭打分弹窗
     * 关闭普通打分弹窗和五维打分弹窗
     */
    private fun dismissScoreDialog() {
        Logger.i(TAG, "dismissScoreDialog")
        if (mScoreDialog?.isShowing == true) {
            mScoreDialog?.dismiss()
            PopupManager.get().dismiss(ScorePopupView::class.java.name, null)
        }

        if (multiScoreDialog?.isShowing==true){
            multiScoreDialog?.dismiss()
        }
    }

    /**
     * 如果需要则播放下一首
     * 根据播放列表和歌曲类型决定是播放下一首还是关闭页面
     */
    fun playNextIfNeed() {
        dismissScoreDialog()
        Logger.d(TAG, "playNextIfNeed PlayConsole.get().replay.value " + PlayConsole.get().replay.value)
        if (PlayerManager.playList.size() <= 0 && PlayConsole.get().editMode.value == false
            || PlayConsole.get().songType.value == SongType.SONG_TYPE_UGC_SONG || PlayConsole.get().songType.value == SongType.SONG_TYPE_KG_MV
            || PlayConsole.get().songType.value == SongType.SONG_TYPE_KSONG_LISTEN
        ) {
            Logger.d(TAG, "finish self")
            getActivity()?.finish()
        } else {
            PlayerManager.playNext()
        }
        if (PlayConsole.get().replay.value == true) {
            return
        }
    }

    /**
     * 判断页面是否还活着
     * @return true表示页面正常,false表示页面已销毁
     */
    fun isAlive(): Boolean {
        val activity = getActivity()
        return activity != null && !activity.isFinishing
    }

    /**
     * 获取Activity实例
     * @return Activity实例,可能为null
     */
    private fun getActivity(): Activity? {
        return when (owner) {
            is Fragment -> {
                (owner as Fragment).activity
            }

            is Activity -> {
                owner as Activity
            }

            else -> {
                null
            }
        }
    }

    /**
     * 返回键内部处理
     * 根据不同场景处理返回键逻辑
     * @param dialog 弹窗对象
     * @param cause 触发原因
     */
    private fun onKeyBackInternal(dialog: DialogInterface?, cause: String?) {
        val scoreScene = PlayConsole.get().scoreResultInfo.value?.scoreScene
        val playState = viewModel.currPlayer()?.playState
        Logger.d(TAG, "$cause, scoreScene: $scoreScene, playState: $playState")
        if (Objects.equals(scoreScene, TotalScoreResultInfo.SCENE_PLAY_NEXT)) {
            if (playState == VideoState.STATE_PAUSE) {
                viewModel.currPlayer()?.resume()
            }
        } else if (Objects.equals(scoreScene, TotalScoreResultInfo.SCENE_EXIT_PAGE)) {
            if (playState == VideoState.STATE_PAUSE) {
                viewModel.currPlayer()?.resume()
            }
        } else {
            playNextIfNeed()
            dialog?.dismiss()
        }
    }
}

FAQ#

唱完一整首时获取打分/录音文件的流程,与唱到一半获取打分/录音文件的流程有何不同?#

唱完一整首时获取打分/录音文件的流程:歌曲自然播放结束 -> sdk回调打分结束 onMultiScoreFinish/onFinishScore(这两个回调只在完整播放完一首歌时才有),获取到打分结果 -> sdk回调播放结束 onPlayEnd(),获取录音文件地址
唱到一半获取打分/录音文件的流程:用户在需要获取打分的时机主动调用player.pause() -> 主动获取分数信息 -> 如果确定结束播放,调用player.stop(),之后等待onPlayEnd()回调获取录音文件地址;如果只获取分数不结束播放,可调用player.resume()恢复播放。
修改于 2026-01-27 07:37:09
上一页
手麦接入文档
下一页
protocal.DelUserWorkReq
Built with