当前位置: 首页 > news >正文

SpringBoot 集成 FFmpeg 解析音视频

文章目录

    • 1 摘要
    • 2 核心 Maven 依赖
    • 3 核心代码
      • 3.1 FFmpeg 解析音视频工具类
      • 3.2 音视频文件信息参数
      • 3.3 音视频文件上传Controller
      • 3.4 application 配置文件
    • 4 测试数据
      • 4.1 视频文件解析
      • 4.2 音频文件解析
    • 5 注意事项
      • 5.1 文件必须在本地
    • 6 推荐参考文档
    • 7 Github 源码

1 摘要

FFmpeg 是最常用的跨平台的音频、视频处理软件,但是其通过命令行的方式进行操作对于普通用户而言上手难度大,同时 FFmpeg 只是函数库,对于不同的编程语言,需要自行适配API接口,以便于操作文件。本文介绍SpringBoot 集成 FFmpeg 实现对音视频文件的解析。

FFmpeg 官网: https://ffmpeg.org

FFmpeg Java 平台常用适配仓库:

JavaCV : https://github.com/bytedeco/javacv

JavaCV 是一个集成第三方函数库的平台,包括 OpenCV、FFmpeg 等知名函数库,提供统一的 API 操作,应用广泛。在不考虑应用程序体积大小的情况下推荐使用 JavaCV 作为集成方案。

JAVE2: https://github.com/a-schild/jave2

JAVE2 是将 FFmpeg 进行封装,并提供 Java API 以供用户操作音视频文件的依赖库。用户可以根据软件运行的操作系统来自由选择所需平台的依赖。

本文是基于 JAVE2 依赖来实现解析音视频文件的功能。

2 核心 Maven 依赖

demo-ffmpeg-media/pom.xml
        <!-- ffmpeg 音视频处理 --><dependency><groupId>ws.schild</groupId><artifactId>jave-core</artifactId><version>${schild-ffmpeg.version}</version></dependency><dependency><groupId>ws.schild</groupId><artifactId>jave-nativebin-win64</artifactId><version>${schild-ffmpeg.version}</version></dependency><dependency><groupId>ws.schild</groupId><artifactId>jave-nativebin-linux64</artifactId><version>${schild-ffmpeg.version}</version></dependency><dependency><groupId>ws.schild</groupId><artifactId>jave-nativebin-osx64</artifactId><version>${schild-ffmpeg.version}</version></dependency><dependency><groupId>ws.schild</groupId><artifactId>jave-nativebin-osxm1</artifactId><version>${schild-ffmpeg.version}</version></dependency>

其中 schild-ffmpeg 版本为:

<schild-ffmpeg.version>3.5.0</schild-ffmpeg.version>

这里分别引入了 Windows、Linux、macOS 系统的依赖,可根据软件运行环境进行删减。

3 核心代码

3.1 FFmpeg 解析音视频工具类

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/common/util/FFmpegMediaUtil.java
package com.ljq.demo.springboot.ffmpeg.common.util;import com.ljq.demo.springboot.ffmpeg.model.response.AudioInfoResponse;
import com.ljq.demo.springboot.ffmpeg.model.response.VideoInfoResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;import java.io.File;
import java.nio.file.Files;
import java.util.Objects;/*** @Description: FFmpeg 音视频工具类* @Author: junqiang.lu* @Date: 2024/5/10*/
@Slf4j
public class FFmpegMediaUtil {/*** 获取视频信息** @param videoPath 视频路径* @return 视频信息*/public static VideoInfoResponse getVideoInfo(String videoPath) {VideoInfoResponse response = null;try {// 解析文件File videoFile = new File(videoPath);MultimediaObject multimediaObject = new MultimediaObject(videoFile);MultimediaInfo multimediaInfo = multimediaObject.getInfo();VideoInfo videoInfo = multimediaInfo.getVideo();// 判断是否为视频if (Objects.isNull(videoInfo) || videoInfo.getBitRate() < 0) {return null;}response = new VideoInfoResponse();response.setFormat(multimediaInfo.getFormat()).setDuration(multimediaInfo.getDuration() / 1000).setSize(videoFile.length()).setMd5(DigestUtils.md5DigestAsHex(Files.readAllBytes(videoFile.toPath())));response.setBitRate(videoInfo.getBitRate()).setFrameRate(videoInfo.getFrameRate()).setWidth(videoInfo.getSize().getWidth()).setHeight(videoInfo.getSize().getHeight());return response;} catch (Exception e) {log.warn("Error processing video file", e);}return response;}/*** 获取音频信息** @param audioPath 音频路径* @return 音频信息*/public static AudioInfoResponse getAudioInfo(String audioPath) {AudioInfoResponse response = null;try {// 解析文件File videoFile = new File(audioPath);MultimediaObject multimediaObject = new MultimediaObject(videoFile);MultimediaInfo multimediaInfo = multimediaObject.getInfo();AudioInfo audioInfo = multimediaInfo.getAudio();// 判断是否为音频if (Objects.isNull(audioInfo) || Objects.nonNull(multimediaInfo.getVideo())) {return null;}response = new AudioInfoResponse();response.setFormat(multimediaInfo.getFormat()).setDuration(multimediaInfo.getDuration() / 1000).setSize(videoFile.length()).setMd5(DigestUtils.md5DigestAsHex(Files.readAllBytes(videoFile.toPath())));response.setSamplingRate(audioInfo.getSamplingRate()).setBitRate(audioInfo.getBitRate()).setChannels(audioInfo.getChannels()).setBitDepth(audioInfo.getBitDepth());return response;} catch (Exception e) {log.warn("Error processing audio file", e);}return response;}}

3.2 音视频文件信息参数

音视频文件公共信息参数

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/model/response/MediaInfoResponse.java
package com.ljq.demo.springboot.ffmpeg.model.response;import lombok.Data;
import lombok.experimental.Accessors;import java.io.Serializable;/*** @Description: 多媒体信息* @Author: junqiang.lu* @Date: 2024/5/10*/
@Data
@Accessors(chain = true)
public class MediaInfoResponse implements Serializable {private static final long serialVersionUID = -326368230008457941L;/*** 文件格式*/private String format;/*** 时长,单位:秒*/private Long duration;/*** 文件大小,单位:字节数*/private Long size;/*** 文件md5值*/private String md5;}

视频文件参数信息对象

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/model/response/VideoInfoResponse.java
package com.ljq.demo.springboot.ffmpeg.model.response;import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;/*** @Description: 视频文件信息返回对象* @Author: junqiang.lu* @Date: 2024/5/10*/
@Data
@Accessors(chain = true)
@ToString(callSuper = true)
public class VideoInfoResponse extends MediaInfoResponse {private static final long serialVersionUID = -9016123624628502571L;/*** 比特率,单位: bps*/private Integer bitRate;/*** 帧率,单位: FPS*/private Float frameRate;/*** 宽度,单位: px*/private Integer width;/*** 高度,单位: px*/private Integer height;}

音频文件参数信息对象

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/model/response/AudioInfoResponse.java
package com.ljq.demo.springboot.ffmpeg.model.response;import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;/*** @Description: 音频文件信息返回对象* @Author: junqiang.lu* @Date: 2024/5/10*/
@Data
@Accessors(chain = true)
@ToString(callSuper = true)
public class AudioInfoResponse extends MediaInfoResponse {private static final long serialVersionUID = 3573655613715240188L;/*** 采样率*/private Integer samplingRate;/*** 音频通道数量,1-单声道,2-立体声*/private Integer channels;/*** 比特率,单位: bps*/private Integer bitRate;/*** 位深度*/private String bitDepth;}

3.3 音视频文件上传Controller

demo-ffmpeg-media/src/main/java/com/ljq/demo/springboot/ffmpeg/controller/FFmpegMediaController.java
package com.ljq.demo.springboot.ffmpeg.controller;import com.ljq.demo.springboot.ffmpeg.common.config.UploadConfig;
import com.ljq.demo.springboot.ffmpeg.common.util.FFmpegMediaUtil;
import com.ljq.demo.springboot.ffmpeg.model.response.AudioInfoResponse;
import com.ljq.demo.springboot.ffmpeg.model.response.VideoInfoResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;/*** @Description: FFmpeg 媒体文件处理控制层* @Author: junqiang.lu* @Date: 2024/5/10*/
@Slf4j
@RestController
@RequestMapping(value = "/api/ffmpeg/media")
public class FFmpegMediaController {@Resourceprivate UploadConfig uploadConfig;/*** 视频上传** @param file* @return* @throws IOException*/@PostMapping(value = "/upload/video", produces = {MediaType.APPLICATION_JSON_VALUE})public ResponseEntity<VideoInfoResponse> uploadVideo(MultipartFile file) throws IOException {// 文件上传保存String videoFilePath = uploadConfig.getUploadPath() + File.separator + file.getOriginalFilename();log.info("videoFilePath: {}", videoFilePath);File videoFile = new File(videoFilePath);if (!videoFile.getParentFile().exists()) {videoFile.getParentFile().mkdirs();}file.transferTo(videoFile);// 获取视频信息VideoInfoResponse videoInfoResponse = FFmpegMediaUtil.getVideoInfo(videoFilePath);return ResponseEntity.ok(videoInfoResponse);}/*** 音频上传** @param file* @return* @throws IOException*/@PostMapping(value = "/upload/audio", produces = {MediaType.APPLICATION_JSON_VALUE})public ResponseEntity<AudioInfoResponse> uploadAudio(MultipartFile file) throws IOException {// 文件上传保存String audioFilePath = uploadConfig.getUploadPath() + File.separator + file.getOriginalFilename();log.info("audioFilePath: {}", audioFilePath);File audioFile = new File(audioFilePath);if (!audioFile.getParentFile().exists()) {audioFile.getParentFile().mkdirs();}file.transferTo(audioFile);// 获取音频信息AudioInfoResponse audioInfoResponse = FFmpegMediaUtil.getAudioInfo(audioFilePath);return ResponseEntity.ok(audioInfoResponse);}}

3.4 application 配置文件

# config
server:port: 9250# spring
spring:application:name: demo-ffmpeg-mediaservlet:multipart:max-file-size: 1000MBmax-request-size: 1000MB# uploadConfig
upload:path: D:\\upload  # linux/macOS 路径 /opt/upload

4 测试数据

示例文件下载: https://zh.getsamplefiles.com

4.1 视频文件解析

测试文件:

https://zh.getsamplefiles.com/download/mp4/sample-4.mp4

测试结果:

{"format": "mov","duration": 30,"size": 7588608,"md5": "d7b5155ee54ee9c7dcd8cbb5395823dc","bitRate": 2015000,"frameRate": 25.0,"width": 1280,"height": 720
}

4.2 音频文件解析

测试文件:

https://zh.getsamplefiles.com/download/mp3/sample-5.mp3

测试结果:

{"format": "mp3","duration": 45,"size": 1830660,"md5": "843e2916b1c552fb5e8ee3d83faddb8c","samplingRate": 44100,"channels": 2,"bitRate": 320000,"bitDepth": "fltp"
}

5 注意事项

5.1 文件必须在本地

在实际项目中,一般会将文件保存到专门的文件服务器,但是 FFmpeg 解析文件必须是本地文件,因此在上传至文件服务器之前需要将文件在本地服务器做中转,解析完毕后再上传,然后删除本地文件。

6 推荐参考文档

SpringBoot集成ffmpeg实现视频转码播放

Convert video to Another Format in Spring Boot(Java-based apps)

springboot如何获取视频文件的视频时间长度

java 视频识别 java 视频转码

JAVE2 官方 Github

JAVE2 官方文档 Getting informations about a multimedia file

javacv-ffmpeg(八)视频文件信息获取

7 Github 源码

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo/tree/master/demo-ffmpeg-media

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.
404Code

相关文章:

SpringBoot 集成 FFmpeg 解析音视频

文章目录 1 摘要2 核心 Maven 依赖3 核心代码3.1 FFmpeg 解析音视频工具类3.2 音视频文件信息参数3.3 音视频文件上传Controller3.4 application 配置文件 4 测试数据4.1 视频文件解析4.2 音频文件解析 5 注意事项5.1 文件必须在本地 6 推荐参考文档7 Github 源码 1 摘要 FFmp…...

基于单片机的直流电机测速装置研究与设计

摘要: 基于单片机的直流电机测速装置采用了对直流电机的中枢供电回路串联取样电阻的方式实现对电机转速的精确实时测量。系统由滤波电路、信号放大电路、单片机控制电路以及稳压电源等功能模块电路构成。工作过程中高频磁环作为载体&#xff0c;利用电磁感应的基本原理对直流电…...

【快捷部署】022_ZooKeeper(3.5.8)

&#x1f4e3;【快捷部署系列】022期信息 编号选型版本操作系统部署形式部署模式复检时间022ZooKeeper3.5.8Ubuntu 20.04tar包单机2024-05-07 一、快捷部署 #!/bin/bash ################################################################################# # 作者&#xff…...

引领AI数据标注新纪元:景联文科技为智能未来筑基

在人工智能蓬勃发展的今天&#xff0c;数据如同燃料&#xff0c;驱动着每一次技术飞跃。在这场智能革命的浪潮中&#xff0c;景联文科技凭借其深厚的专业实力与前瞻性的战略眼光&#xff0c;正站在行业前沿&#xff0c;为全球的人工智能企业提供坚实的数据支撑。 全国布局&…...

多模态大语言模型和 Apple 的 MM1

原文地址&#xff1a;multimodal-large-language-models-apples-mm1 2024 年 4 月 13 日 抽象是计算机科学中最关键的概念之一&#xff0c;具有一些最强大的影响。从简单的角度来看&#xff0c;抽象就是将某一事物应用于多种不同情况的能力。例如&#xff0c;如果你创造了一种…...

算法day04

第一题 &#xff1a; 209. 长度最小的子数组 有上题可知&#xff0c;我们会采用双指针和单调性的思路来解决 我们本题采用左右双指针从数组的0位置同向前进&#xff0c;所以将此类模型称为滑块&#xff1b; 步骤思路如下&#xff1a; 步骤一&#xff1a; 定义所有双指针都指向…...

电信网关配置管理系统 rewrite.php 文件上传致RCE漏洞复现

0x01 产品简介 中国电信集团有限公司(英文名称“China Telecom”、简称“中国电信”)成立于2000年9月,是中国特大型国有通信企业、上海世博会全球合作伙伴。电信网关配置管理系统是一个用于管理和配置电信网络中网关设备的软件系统。它可以帮助网络管理员实现对网关设备的远…...

从零学算法14

14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1&#xff1a; 输入&#xff1a;strs [“flower”,“flow”,“flight”] 输出&#xff1a;“fl” 示例 2&#xff1a; 输入&#xff1a;strs [“d…...

[入门] Unity Shader前置知识(5) —— 向量的运算

在Unity中&#xff0c;向量无处不在&#xff0c;我想很多人都使用过向量类的内置方法 normalized() 吧&#xff0c;我们都知道该方法是将其向量归一化从而作为一个方向与速度相乘&#xff0c;以达到角色朝任一方向移动时速度都相等的效果&#xff0c;但内部具体是如何将该向量进…...

html的i标签 “\e905“ font-family 字体没有效果

一、html的i标签 “\e905” 没有效果 在HTML和CSS中&#xff0c;\e905 这样的字符通常与字体图标&#xff08;Font Icons&#xff09;或自定义字体&#xff08;Custom Fonts&#xff09;中的Unicode字符相关。具体来说&#xff0c;\e905 是一个Unicode转义序列&#xff0c;但它…...

Golang reflect.MakeFunc() 的用法及示例

Golang 作为一门强类型语言&#xff0c;在某些场景下&#xff0c;我们需要动态地创建函数或者修改函数&#xff0c;这个时候就可以使用反射的方法去实现。在反射中&#xff0c;我们可以使用 reflect.MakeFunc() 方法来创建一个新的函数&#xff0c;本文我将介绍使用反射及其 Ma…...

深入学习和理解Django视图层:处理请求与响应

title: 深入学习和理解Django视图层&#xff1a;处理请求与响应 date: 2024/5/4 17:47:55 updated: 2024/5/4 17:47:55 categories: 后端开发 tags: Django请求处理响应生成模板渲染表单处理中间件异常处理 第一章&#xff1a;Django框架概述 1.1 什么是Django&#xff1f;…...

【MySQL】SQL基本知识点DDL(1)

目录 1.SQL分类&#xff1a; 2.DDL-数据库操作 3.DDL-表操作-创建 4.DDL-表操作-查询 5.DDL-表操作-数据类型 6.DDL-表操作-修改 1.SQL分类&#xff1a; 2.DDL-数据库操作 3.DDL-表操作-创建 注意&#xff1a;里面的符号全部要切换为英文状态 4.DDL-表操作-查询 5.DDL…...

短剧奔向小程序,流量生意如何开启?

随着移动互联网的飞速发展&#xff0c;小程序作为一种轻量级、易传播的应用形态&#xff0c;逐渐在各个领域展现出其独特的商业价值。而最近爆火的短剧小视频作为一种受众广泛的娱乐形式&#xff0c;与小程序结合后&#xff0c;不仅为观众提供了更为便捷的观看体验&#xff0c;…...

微服务下的技术栈架构解析

微服务是一种架构风格&#xff0c;它将一个复杂的应用拆分成多个独立自治的服务&#xff0c;每个服务负责应用程序中的一小部分功能。这些服务通过定义良好的API进行通信&#xff0c;通常是HTTP RESTful API或事件流。微服务架构的主要特点包括单一职责、自治性、可独立部署和扩…...

Mesa3D图形库与NIR(New Intermediate Representation)

Mesa 是一个开源图形库&#xff0c;为 Unix 和 Linux 系统提供了 OpenGL 和 Vulkan API 的实现。它也支持其他图形 API&#xff0c;如OpenCL、OpenGL ES 和 Vulkan。Mesa 项目的目标是为开源社区提供高性能的图形库&#xff0c;使得开源操作系统能够充分利用现代图形硬件。 Me…...

C++:模板初阶

文章目录 泛型编程函数模板概念函数模板格式函数模板的原理函数模板的实例化模板参数的匹配原则 模板类类模板的定义格式类模板实例化 泛型编程 如何实现一个通用的交换函数呢&#xff1f; 函数重载可以帮助我们完成 void Swap(int& left, int& right) {int temp l…...

为什么要学Python?学Python有什么用?

为什么要学Python&#xff1f;学Python有什么用&#xff1f; 在当今的数字化时代&#xff0c;编程已成为一项宝贵的技能。Python&#xff0c;作为一种流行的编程语言&#xff0c;因其易于学习和强大的功能而受到全球开发者的青睐。本文将探讨学习Python的原因和它的实际应用&am…...

Linux磁盘IO、网络IO、零拷贝详解

一、什么是I/O&#xff1f; 在计算机操作系统中&#xff0c;所谓的I/O就是输入&#xff08;input&#xff09;和输出&#xff08;output&#xff09;,也可以理解为读&#xff08;read&#xff09;和写&#xff08;write&#xff09;,针对不同的对象&#xff0c;I/O模式可以划分…...

工业交换机外壳材质大比拼,看看哪种外壳适合你

在工业领域里&#xff0c;交换机就像我们的网络心脏&#xff0c;时刻跳动着确保信息畅通无阻。而它的外壳&#xff0c;就是保护这颗“心脏”的铠甲。今天&#xff0c;咱们就来聊聊这些铠甲——工业交换机外壳的材质和防护等级&#xff0c;看看它们如何守护我们的网络世界。 首…...

智慧公厕的技术基础、保障技术和应用价值

近年来&#xff0c;随着信息技术的快速发展&#xff0c;智慧公厕逐渐成为城市管理的热点项目。智慧公厕利用物联网技术与大数据、云计算、网络通信、自动化控制等先进技术相结合&#xff0c;公共厕所的管理变得更加快捷高效&#xff0c;实现了真正的智能化使用和智慧化管理。下…...

思腾合力受邀参加VALSE 2024视觉与学习青年学者研讨会

在充满学术氛围的五月&#xff0c;思腾合力荣幸受邀参加了于2024年5月5-7日在重庆举行的第十四届VALSE大会。作为视觉与学习领域的顶级交流平台&#xff0c;VALSE大会每年都吸引着全国专家与学者的目光。 本次大会不仅延续了往届的高水平学术研讨&#xff0c;还进一步拓宽了研究…...

geotrust dv通配符证书800

Geotrust是成立时间较久的正规CA认证机构&#xff0c;在过去的几十年间颁发了无数的SSL证书&#xff0c;这些SSL证书被各个开发者使用&#xff0c;受到大多数浏览器的信任。而Geotrust旗下的DV通配符证书因其广泛的应用范围受到了用户的青睐。今天就随SSL盾小编了解Geotrust旗下…...

SpringBoot工作原理

优点&#xff1a;自动装配&#xff0c;起步依赖 起步依赖 原理就是maven的依赖传递 【A依赖B、B依赖C….&#xff0c;则我导入依赖A的时候&#xff0c;B&#xff0c;C都会被maven加载进来】 重点看看自动装配 概念&#xff1a; 当Spring容器启动后&#xff0c;一些配置类、…...

【Spring】Spring 整合 Junit、MyBatis

一、 Spring 整合 Junit <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache…...

【JVM基础篇】JVM入门介绍

JVM入门介绍 为什么学习JVM 岗位要求 解决工作中遇到的问题 性能调优 真实案例 导出超大文件&#xff0c;系统崩溃从数据库中查询超大量数据出错消费者消费来不及导致系统崩溃Mq消息队列接受消息导致的内存泄漏业务高峰期系统失去响应 初识JVM 什么是JVM&#xff1f; JV…...

《21天学通C++》(第二十一章)理解函数对象

什么是函数对象&#xff1f; 函数对象是一种特殊类型的类&#xff0c;它重载了函数调用操作符 operator()&#xff0c;使得类的实例可以像函数一样被调用。 什么是谓词&#xff1f; 谓词是指一个能够返回布尔值&#xff08;true或false&#xff09;的函数或函数对象 1.一元函数…...

2024.1.1 IntelliJ IDEA 使用记录

2024.1.1 IntelliJ IDEA 使用记录 下载设置文件编码maven 配置 插件可以中文语言包安装lombok 插件Smart Tomcat ( 根据需要安装)Smart Tomcat 配置 热部署&#xff08;非必须的&#xff09;解决Intellij IDEA运行报Command line is too long的问题 项目导入java 设置maven 配置…...

扩展van Emde Boas树以支持卫星数据:设计与实现

扩展van Emde Boas树以支持卫星数据&#xff1a;设计与实现 1. 引言2. vEB树的基本概念3. 支持卫星数据的vEB树设计3.1 数据结构的扩展3.2 操作的修改3.3 卫星数据的存储和检索 4. 详细设计和实现4.1 定义卫星数据结构体4.2 修改vEB树节点结构4.3 插入操作的伪代码4.4 C语言实现…...

玩游戏专用远程控制软件

玩游戏专用远程控制软件&#xff1a;实现远程游戏的新体验 随着网络技术的不断发展和创新&#xff0c;远程控制软件已经逐渐渗透到我们生活的方方面面&#xff0c;尤其是在游戏领域。玩游戏专用远程控制软件&#xff0c;作为这一趋势下的产物&#xff0c;为玩家提供了全新的游…...