如何在VR头显端实现低延迟的RTSP或RTMP播放

技术背景

VR(虚拟现实技术)给我们带来身临其境的视觉体验,广泛的应用于城市规划、教育培训、工业仿真、房地产、水利电力、室内设计、文旅、军事等众多领域,常用的行业比如:

  • 教育行业:VR头显可以用于教育培训,提供沉浸式的教学体验,例如虚拟实验室、虚拟课堂等,帮助学生更好地理解和掌握知识。
  • 医疗行业:VR头显可以用于医疗训练和治疗,例如手术模拟、康复训练等,提高医疗效果和质量。
  • 文旅行业:VR头显可以用于旅游娱乐,提供沉浸式的旅游体验,例如虚拟旅游、文化遗产展示等。
  • 房地产行业:VR头显可以用于房地产展示,提供更加真实、直观的房屋展示和体验,帮助客户更好地了解和选择房屋。
  • 展览展示行业:VR头显可以用于展览展示,提供沉浸式的展览体验,例如虚拟展厅、虚拟展品等,吸引观众的注意和参与。
  • 军事行业:VR头显可以用于军事训练和作战指挥,提供更加真实、逼真的军事训练环境。

技术实现

如何在VR头显实现RTMP或RTSP播放?

VR头显播放RTMP或RTSP流数据,简单来说,通过jni层打通RTMP或RTSP流传输,解包并解码回调给Unity YUV或RGB数据,Unity场景下,绘制即可,本文以大牛直播SDK的Unity平台RTMP、RTSP播放为例,介绍下具体技术实现:

开始播放:

    public void Play(){if (is_running){Debug.Log("已经在播放。。");   return;}//获取输入框的urlstring url = input_url_.text.Trim();OpenPlayer();if ( player_handle_ == 0 )return;NT_U3D_Set_Game_Object(player_handle_, game_object_);/* ++ 播放前参数配置可加在此处 ++ */int is_using_tcp = 0;        //TCP/UDP模式设置NT_U3D_SetRTSPTcpMode(player_handle_, is_using_tcp);int is_report = 0;int report_interval = 1;NT_U3D_SetReportDownloadSpeed(player_handle_, is_report, report_interval);  //下载速度回调NT_U3D_SetBuffer(player_handle_, play_buffer_time_);                        //设置buffer timeNT_U3D_SetPlayerLowLatencyMode(player_handle_, is_low_latency_ ? 1 : 0);    //设置是否启用低延迟模式NT_U3D_SetMute(player_handle_, is_mute_ ? 1 : 0);                           //是否启动播放的时候静音NT_U3D_SetAudioVolume(player_handle_, cur_audio_volume_);                   //设置播放音量NT_U3D_SetVideoDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0);          //设置H.264软硬解模式NT_U3D_SetVideoHevcDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0);          //设置H.265软硬解模式int is_fast_startup = 1;NT_U3D_SetFastStartup(player_handle_, is_fast_startup);                     //设置快速启动模式int rtsp_timeout = 10;NT_U3D_SetRTSPTimeout(player_handle_, rtsp_timeout);                        //设置RTSP超时时间int is_auto_switch_tcp_udp = 1;NT_U3D_SetRTSPAutoSwitchTcpUdp(player_handle_, is_auto_switch_tcp_udp);    //设置TCP/UDP模式自动切换int is_audiotrack = 1;NT_U3D_SetAudioOutputType(player_handle_, is_audiotrack);                   //设置音频输出模式: if 0: 自动选择; if with 1: audiotrack模式NT_U3D_SetUrl(player_handle_, videoUrl);/* -- 播放前参数配置可加在此处 -- */int flag = NT_U3D_StartPlay(player_handle_);if (flag  == DANIULIVE_RETURN_OK){is_need_get_frame_ = true;Debug.Log("播放成功");}else{is_need_get_frame_ = false;Debug.LogError("播放失败");}is_running = true;  }

Close Player:

    private void ClosePlayer(){is_need_get_frame_ = false;is_need_init_texture_ = false;int flag = NT_U3D_StopPlay(player_handle_);if (flag == DANIULIVE_RETURN_OK){Debug.Log("停止成功");}else{Debug.LogError("停止失败");}flag = NT_U3D_Close(player_handle_);if (flag == DANIULIVE_RETURN_OK){Debug.Log("关闭成功");}else{Debug.LogError("关闭失败");}player_handle_ = 0;NT_U3D_UnInit();is_running = false;video_width_ = 0;video_height_ = 0;}

Event事件回调处理:

    /// <summary>/// android 传递过来 code/// </summary>/// <param name="event_message"></param>public void onNTSmartEvent(string event_message){if (null == event_message || event_message.Length < 1)return;string[] strs = event_message.Split(',');if (null == strs || strs.Length < 6)return;string player_handle =strs[0];string code = strs[1];string param1 = strs[2];string param2 = strs[3];string param3 = strs[4];string param4 = strs[5];Debug.Log("[daiusdk] code: 0x" + Convert.ToString(Convert.ToInt32(code), 16));String player_event = "";switch (Convert.ToInt32(code)){case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:player_event = "开始..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:player_event = "连接中..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:player_event = "连接失败..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:player_event = "连接成功..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:player_event = "连接断开..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP:player_event = "停止播放..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:player_event = "分辨率信息: width: " + Convert.ToInt32(param1) + ", height: " + Convert.ToInt32(param2);break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:player_event = "收不到媒体数据,可能是url错误..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:player_event = "切换播放URL..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:player_event = "快照: " + param1 + " 路径:" + param3;if (Convert.ToInt32(param1) == 0){player_event = "截取快照成功..";}else{player_event = "截取快照失败..";}break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:player_event = "[record]开始一个新的录像文件 : " + param3;break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:player_event = "[record]已生成一个录像文件 : " + param3;break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:player_event = "Start_Buffering..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:player_event = "Buffering: " + Convert.ToInt32(param1);break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:player_event = "Stop_Buffering..";break;case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:player_event = "download_speed:" + param1 + "Byte/s" + ", "+ (Convert.ToInt32(param1) * 8 / 1000) + "kbps" + ", " + (Convert.ToInt32(param1) / 1024)+ "KB/s";break;}Debug.Log(player_event);player_event = null;strs = null;}

如何封装实现原生jni层交互:

    /// SmartPlayerAndroidMono.cs/// Author: daniusdk.com///Created on 2018/05/10/// <summary>/// Init/// </summary>public int NT_U3D_Init(){return player_obj_.Call<int>("Init", java_obj_cur_activity_);}/// <summary>/// 开始/// 返回播放句柄/// </summary>public long NT_U3D_Open(){return player_obj_.Call<long>("Open");}/// <summary>/// Register Game Object,用于消息传递/// </summary>public int NT_U3D_Set_Game_Object(long handle, string gameObjectName){return player_obj_.Call<int>("SetGameObject", handle, gameObjectName);}/// <summary>/// 设置H.264解码方式 false 软件解码 true 硬件解码 默认为false/// </summary>/// <param name="isHwDecoder"></param>public int NT_U3D_SetVideoDecoderMode(long handle, int isHwDecoder){return player_obj_.Call<int>("SetPlayerVideoHWDecoder", handle, isHwDecoder);}/// <summary>/// 设置H.265 解码方式 false 软件解码 true 硬件解码 默认为false/// </summary>/// <param name="isHevcHwDecoder"></param>public int NT_U3D_SetVideoHevcDecoderMode(long handle, int isHevcHwDecoder){return player_obj_.Call<int>("SetPlayerVideoHevcHWDecoder", handle, isHevcHwDecoder);}/// <summary>/// 设置音频输出模式: if 0: 自动选择; if with 1: audiotrack模式/// </summary>/// <param name="use_audiotrack"></param>public int NT_U3D_SetAudioOutputType(long handle, int use_audiotrack){return player_obj_.Call<int>("SetAudioOutputType", handle, use_audiotrack);}/// <summary>/// 设置播放端缓存大小, 默认200毫秒/// </summary>/// <param name="buffer"></param>public int NT_U3D_SetBuffer(long handle, int buffer){return player_obj_.Call<int>("SetBuffer", handle, buffer);}/// <summary>/// 接口可实时调用:设置是否实时静音,1:静音; 0: 取消静音/// </summary>/// <param name="is_mute"></param>public int NT_U3D_SetMute(long handle, int is_mute){return player_obj_.Call<int>("SetMute", handle, is_mute);}/// <summary>/// 接口可实时调用:设置播放音量,范围是[0, 100], 0是静音,100是最大音量, 默认是100/// </summary>/// <param name="audio_volume"></param>public int NT_U3D_SetAudioVolume(long handle, int audio_volume){return player_obj_.Call<int>("SetAudioVolume", handle, audio_volume);}/// <summary>/// 设置RTSP TCP模式, 1: TCP; 0: UDP/// </summary>/// <param name="is_using_tcp"></param>public int NT_U3D_SetRTSPTcpMode(long handle, int is_using_tcp){return player_obj_.Call<int>("SetRTSPTcpMode", handle, is_using_tcp);}/// <summary>/// 设置RTSP超时时间, timeout单位为秒,必须大于0/// </summary>/// <param name="timeout"></param>public int NT_U3D_SetRTSPTimeout(long handle, int timeout){return player_obj_.Call<int>("SetRTSPTimeout", handle, timeout);}/// <summary>/// 设置RTSP TCP/UDP自动切换/// NOTE: 对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式./// 为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp./// </summary>/// <param name="timeout"></param>/// timeout:如果设置1的话, sdk将在tcp和udp之间尝试切换播放,如果设置为0,则不尝试切换.public int NT_U3D_SetRTSPAutoSwitchTcpUdp(long handle, int is_auto_switch_tcp_udp){return player_obj_.Call<int>("SetRTSPAutoSwitchTcpUdp", handle, is_auto_switch_tcp_udp);}/// <summary>/// 设置快速启动该模式,/// </summary>/// <param name="is_fast_startup"></param>public int NT_U3D_SetFastStartup(long handle, int is_fast_startup){return player_obj_.Call<int>("SetFastStartup", handle, is_fast_startup);}/// <summary>/// 设置超低延迟模式 false不开启 true开启 默认false/// </summary>/// <param name="mode"></param>public int NT_U3D_SetPlayerLowLatencyMode(long handle, int mode){return player_obj_.Call<int>("SetPlayerLowLatencyMode", handle, mode);}/// <summary>/// 设置视频垂直反转/// is_flip: 0: 不反转, 1: 反转/// </summary>/// <param name="is_flip"></param>public int NT_U3D_SetFlipVertical(long handle, int is_flip){return player_obj_.Call<int>("SetFlipVertical", handle, is_flip);}/// <summary>/// 设置视频水平反转/// is_flip: 0: 不反转, 1: 反转/// </summary>/// <param name="is_flip"></param>public int NT_U3D_SetFlipHorizontal(long handle, int is_flip){return player_obj_.Call<int>("SetFlipHorizontal", handle, is_flip);}/// <summary>/// 设置顺时针旋转, 注意除了0度之外, 其他角度都会额外消耗性能/// degress: 当前支持 0度,90度, 180度, 270度 旋转/// </summary>/// <param name="degress"></param>public int NT_U3D_SetRotation(long handle, int degress){return player_obj_.Call<int>("SetRotation", handle, degress);}/// <summary>/// 设置是否回调下载速度/// is_report: if 1: 上报下载速度, 0: 不上报./// report_interval: 上报间隔,以秒为单位,>0./// </summary>/// <param name="is_report"></param>/// <param name="report_interval"></param>public int NT_U3D_SetReportDownloadSpeed(long handle, int is_report, int report_interval){return player_obj_.Call<int>("SetReportDownloadSpeed", handle, is_report, report_interval);}/// <summary>/// 设置是否需要在播放或录像过程中快照/// </summary>/// <param name="is_save_image"></param>public int NT_U3D_SetSaveImageFlag(long handle, int is_save_image){return player_obj_.Call<int>("SetSaveImageFlag", handle, is_save_image);}/// <summary>/// 播放或录像过程中快照/// </summary>/// <param name="imageName"></param>public int NT_U3D_SaveCurImage(long handle, string imageName){return player_obj_.Call<int>("SaveCurImage", handle, imageName);}/// <summary>/// 播放或录像过程中,快速切换url/// </summary>/// <param name="uri"></param>public int NT_U3D_SwitchPlaybackUrl(long handle, string uri){return player_obj_.Call<int>("SwitchPlaybackUrl", handle, uri);}/// <summary>/// 创建录像存储路径/// </summary>/// <param name="path"></param>public int NT_U3D_CreateFileDirectory(string path){return player_obj_.Call<int>("CreateFileDirectory", path);}/// <summary>/// 设置录像存储路径/// </summary>/// <param name="path"></param>public int NT_U3D_SetRecorderDirectory(long handle, string path){return player_obj_.Call<int>("SetRecorderDirectory", handle, path);}/// <summary>/// 设置单个录像文件大小/// </summary>/// <param name="size"></param>public int NT_U3D_SetRecorderFileMaxSize(long handle, int size){return player_obj_.Call<int>("SetRecorderFileMaxSize", handle, size);}/// <summary>/// 设置录像时音频转AAC编码的开关/// aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能./// 注意: 转码会增加性能消耗/// </summary>/// <param name="is_transcode"></param>/// is_transcode:设置为1的话,如果音频编码不是aac,则转成aac,如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.public int NT_U3D_SetRecorderAudioTranscodeAAC(long handle, int is_transcode){return player_obj_.Call<int>("SetRecorderAudioTranscodeAAC", handle, is_transcode);}/// <summary>/// 设置播放路径/// </summary>public int NT_U3D_SetUrl(long handle, string url){return player_obj_.Call<int>("SetUrl", handle, url);}/// <summary>/// 开始播放/// </summary>public int NT_U3D_StartPlay(long handle){return player_obj_.Call<int>("StartPlay", handle);}/// <summary>/// 获取YUV数据/// </summary>public AndroidJavaObject NT_U3D_GetVideoFrame(long handle){return player_obj_.Call<AndroidJavaObject>("GetVideoFrame", handle);}/// <summary>/// 停止播放/// </summary>public int NT_U3D_StopPlay(long handle){return player_obj_.Call<int>("StopPlay", handle);}/// <summary>/// 开始录像/// </summary>public int NT_U3D_StartRecorder(long handle){return player_obj_.Call<int>("StartRecorder", handle);}/// <summary>/// 停止录像/// </summary>public int NT_U3D_StopRecorder(long handle){return player_obj_.Call<int>("StopRecorder", handle);}/// <summary>/// 关闭播放/// </summary>public int NT_U3D_Close(long handle){return player_obj_.Call<int>("Close", handle);}/// <summary>/// UnInit Player/// </summary>public int NT_U3D_UnInit(){return DANIULIVE_RETURN_OK;}

技术总结

通过实际测试来看,VR头显端,如果设备性能尚可的话,播放RTMP或RTSP,可实现毫秒级的延迟,可满足大多数有交互诉求的技术场景,此外,如果头显端支持硬解码的话,可以优先考虑硬解码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/57630.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

淘宝API技术解析,实现按图搜索淘宝商品

淘宝提供了开放平台接口&#xff08;API&#xff09;来实现按图搜索淘宝商品的功能。您可以通过以下步骤来实现&#xff1a; 1. 获取开放平台的访问权限&#xff1a;首先&#xff0c;您需要在淘宝开放平台创建一个应用&#xff0c;获取访问淘宝API的权限。具体的申请步骤和要求…

前端开发之Element Plus的分页组件el-pagination显示英文转变为中文

前言 在使用element的时候分页提示语句是中文的到了element-plus中式英文的&#xff0c;本文讲解的就是怎样将英文转变为中文 效果图 解决方案 如果你的element-plus版本为2.2.29以下的 import { createApp } from vue import App from ./App.vue import ElementPlus from …

最新本地大模型进展#Chinese-LLaMA-2支持16k长上下文

‍‍ Hi&#xff0c;今天为大家介绍最新的本地中文语言模型进展。 [2023/08/25] Chinese-LLaMA-2发布了新的更新&#xff1a; 长上下文模型Chinese-LLaMA-2-7B-16K和Chinese-LLaMA-2-13B-16K&#xff0c;支持16K上下文&#xff0c;并可通过NTK方法进一步扩展至24K。 这意味着在…

uniapp 实现地图距离计算

在uniapp中实现地图距离计算可以借助第三方地图服务API来实现。以下是一种基本的实现方式&#xff1a; 注册地图服务API账号&#xff1a;你可以选择使用高德地图、百度地图等提供地图服务的厂商&#xff0c;注册一个开发者账号并获取API密钥。 安装相关插件或SDK&#xff1a;根…

区块链BaaS篇

区块链BaaS&#xff08;Blockchain as a Service&#xff09;区块链即服务&#xff1b;感觉5年前做的BaaS和现在做的BaaS没啥区别&#xff0c;换了批人重复造轮子&#xff0c;BaaS做的越来越乱&#xff0c;也越来越中心化。BaaS是方便区块链调用的工具&#xff0c;工具是方便使…

人力资源小程序的设计方案与实现

随着互联网的发展&#xff0c;人才招聘已经成为许多企业的一项重要任务。为了提高招聘效率和便利求职者&#xff0c;许多企业开始采用小程序作为招聘平台。本文将为大家介绍一个搭建本地人才招聘网小程序的实用指南。 首先&#xff0c;我们需要登录【乔拓云】制作平台&#xff…

checkstyle检查Java编程样式:识别应该被定义为final的类

介绍 总体说明 checkstyle可以使用FinalClass检查应该被定为final的类。如果违反了&#xff0c;就会报违反项&#xff1a; https://checkstyle.sourceforge.io/checks/design/finalclass.html checkstyle规则集文件对FinalClass模块的配置&#xff1a; 哪些类可以被定义fi…

React笔记(二)JSX

一、JSX JSX是javascript XML的简写&#xff0c;实际上是javascript的扩展&#xff0c;既有javascript的语法结构&#xff0c;又有XML的结构 1、JSX的规则要求 jsx必须要有一个根节点 如果不想产生无用的根标签&#xff0c;但是还要遵守JSX的语法的要求&#xff0c;可以使用…

Docker harbor 私有仓库的部署和管理

目录 一、什么是Harbor 二、Harbor的特性 三、Harbor的构成 四、部署配置Docker Harbor 1. 首先需要安装 Docker-Compose 服务 2.部署 Harbor 服务 3.使用harbor仓库 &#xff08;1&#xff09;项目管理 &#xff08;2&#xff09;用户管理 一、什么是Harbor Harbor …

飞桨中的李宏毅课程中的第一个项目——PM2.5的预测

所谓的激活函数&#xff0c;就是李宏毅老师讲到的sigmoid函数 和 hard sigmoid函数 &#xff0c;ReLU函数那些 现在一点点慢慢探索&#xff0c;会成为日后想都做不到的经历&#xff0c;当你啥也不会的时候&#xff0c;才是慢慢享受探索的过程。 有一说一&#xff0c;用chatGP…

安卓版yolo-fastest

安卓版本yolofastest效果测试 安卓配置OPENCV4ANDROID&#xff0c;见我的博客一篇文章opencv4dandroid配置 这个不需要使用JNI&#xff0c;十分简单的配置 说真的&#xff0c;其实只调用OPENCV的函数&#xff0c;自己写的代码不多&#xff0c;使用OPENCV4ANDROID和JNI的时间差…

项目经理常用工具01

主要工具 表达工具—SCRTV方法 情境 Scene&#xff1a;明确问题&#xff1a;是什么&#xff1f;冲突 Confilict&#xff1a;提出疑问&#xff1a;怎么了?原因 Reason&#xff1a;分析原因&#xff1a;为什么&#xff1f;策略 Tactics&#xff1a;进行决策&#xff1a;怎么办…

数据建模在MES管理系统中的作用,以及合理设计

随着信息化的快速发展&#xff0c;MES系统解决方案在企业中的应用越来越广泛。作为实现生产过程数字化的重要组成部分&#xff0c;MES系统扮演着监控、控制和优化生产运营的关键角色。而在MES系统中&#xff0c;数据建模是实现高效生产管理的重要环节之一。本文将探讨数据建模在…

自定义node-red节点中,如何编写节点的配置信息弹窗

前言 最近有读者通过博客向我咨询,在自定义node-red节点时,如何编写该节点的配置页面,就是我们通常见到的,双节节点打开的信息弹窗。如下图: 上面两张图,展示了inject节点与mqtt in 节点的配置弹窗。 在弹窗中,除了上面的删除,取消,完成,和下面的失效按钮。 中间…

Docker容器中的Postgresql备份脚本异常解决办法

本文基于K8S中Docker容器对postgres数据库进行备份的操作&#xff0c;编写好脚本后&#xff0c;手动执行脚本是正常的&#xff0c;但是crontab定时实行却报错&#xff0c;报错信息为kubectl command not found&#xff0c;提示没有找到kubectl指令。 本文主要介绍对该报错信息…

二级MySQL(二)——编程语言,函数

SQL语言又称为【结构化查询语言】 请使用FLOOR&#xff08;x&#xff09;函数求小于或等于5.6的最大整数 请使用TRUNCATE&#xff08;x&#xff0c;y&#xff09;函数将数字1.98752895保留到小数点后4位 请使用UPPER&#xff08;&#xff09;函数将字符串‘welcome’转化为大写…

C语言的发展及特点

1. C语言的发展历程 C语言作为计算机编程领域的重要里程碑&#xff0c;其发展历程承载着无数开发者的智慧和创新。C语言诞生于20世纪70年代初&#xff0c;由计算机科学家Dennis Ritchie在贝尔实验室首次推出。当时&#xff0c;Ritchie的目标是为Unix操作系统开发一门能够更方便…

大数据平台安全主要是指什么安全?如何保障?

大数据时代已经来临&#xff0c;各种数据充斥着我们的生活与工作。随着数据的多样性以及复杂性以及大量性&#xff0c;大数据平台诞生了。但对于大数据平台大家都不是很了解&#xff0c;有人问大数据平台安全主要是指什么安全&#xff1f;如何保障&#xff1f; 大数据平台安全…

睿趣科技:抖音开网店卖玩具怎么样

近年来&#xff0c;随着社交媒体平台的飞速发展&#xff0c;抖音作为一款短视频分享应用也迅速崭露头角。而在这个充满创业机遇的时代背景下&#xff0c;许多人开始探索在抖音平台上开设网店&#xff0c;尤其是卖玩具类商品&#xff0c;那么抖音开网店卖玩具究竟怎么样呢? 首先…

如何清空小程序会员卡的电子票

​电子票不仅方便了用户的购票和消费&#xff0c;还提升了用户的购物体验和忠诚度。然而&#xff0c;在一些特殊情况下&#xff0c;可能需要手动清空会员的电子票。那么&#xff0c;下面我们就来探讨一下在小程序中如何手动清空会员的电子票。 1. 找到指定的会员卡。在管理员后…