二.音视频编辑-媒体组合-播放
引言
当涉及到音视频编辑时,媒体资源的提取和组合是至关重要的环节。在iOS平台上,AVFoundation框架提供了丰富而强大的功能,使得媒体资源的操作变得轻松而高效。从原始的媒体中提取片段,然后将它们巧妙地组合成一个完整的作品,这是音视频编辑过程中的常见任务之一。在这篇博客中,我们将深入探讨iOS AVFoundation框架中的媒体组合功能,探索其如何为开发者提供丰富的工具和技术,帮助他们实现创意无限的音视频编辑项目。
概述
上图是关于媒体功能中的核心类,以及类直接的关系图。有关资源组合的功能就源于AVAsset的子类AVComposition。一个组合就是将多种媒体资源组合成一个自定义的临时排列,再将这个临时排列视为一个可呈现的独立媒体项目。就比如AVAsset对象,组合相当于包含了一个或多个给定类型的媒体轨道的容器。AVComposition中的轨道都是AVAssetTrack的子类AVCompositionTrack。一个组合轨道本身由一个或多个媒体片段组成,由AVCompositionTrackSegment类定义,代表这个组合中的实际媒体区域。
组合后的对象关系如下:
AVComposition和AVCompositionTrack都是不可变对象,提供对资源的只读操作。这些对象提供了一个合适的接口让应用程序的一部分可以进行播放或处理。不过,当创建自己的组合时,就需要使用AVMutableComposition和AVMutableCompositionTrack所提供的可变子类。这些对象提供的类接口需要操作轨道和轨道分段,这样我们就可以创建所需的临时排列了。
基础方法
这个基础的实例会将两个视频片段中的前5秒内容提取出来,并按照组合视频轨道的顺序进行排序。还会从MP3文件中奖音频轨道整合到视频中,期间会用到Core Media框架中定义的CMTime数据类型作为时间格式,相关内容可以查看其它博客。
let composition = AVMutableComposition()var videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!var audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
上面的示例创建了一个AVMutableComposition并用它的addMutableTrackWithMediaType:preferredTrackID:方法添加了两个轨道对象。当创建组合轨道时,开发者必须指明它所能支持的媒体类型,并给出一个轨道标识符。设置preferredTrackID:参数为CMPersistentTrackID,这是一个32位的整数值。虽然我们可以传递任意标识符作为参数,这个标识符在我们之后需要返回轨道时会用到,不过一般来说都是赋给它一个kCMPersistentTrackID_Invalid常量。这个有着奇怪名字的常量的意思是我们需要创建一个合适轨道ID的任务委托给框架,标识符会以1..n排列。
现在我们已经实现了一个组合资源:
下一步就是将独立的媒体片段插入到组合的轨道中。
//1.创建资源let goldenGateAsset = AVURLAsset(url: URL(string: "1")!, options: nil)let teaGardenAsset = AVURLAsset(url: URL(string: "2")!, options: nil)let soundTrackAsset = AVURLAsset(url: URL(string: "3")!, options: nil)//2.定义插入点var cursorTime = CMTime.zero//3.定义片段时长let videoDuration = CMTime(value: 5, timescale: 1)let videoTimeRange = CMTimeRange(start: cursorTime, duration: videoDuration)//4.提取资源中的视频轨道并插入到组合中的视频轨道let goldenGateAssetTrack = goldenGateAsset.tracks(withMediaType: .video).first!do {try videoTrack.insertTimeRange(videoTimeRange, of: goldenGateAssetTrack, at: cursorTime)} catch {print("Error inserting time range: \(error)")}//5.调整插入时间cursorTime = CMTimeAdd(cursorTime, videoDuration) //6.提取资源中的视频轨道并插入到组合中的视频轨道let teaGardenAssetTrack = teaGardenAsset.tracks(withMediaType: .video).first!do {try videoTrack.insertTimeRange(videoTimeRange, of: teaGardenAssetTrack, at: cursorTime)} catch {print("Error inserting time range: \(error)")}//7.调整插入时间和时长cursorTime = CMTime.zerolet audioDuration = composition.durationlet audioTimeRange = CMTimeRangeMake(start: cursorTime, duration: audioDuration)//8.提取音频轨道并插入到组合中的音频轨道let soundTrackAssetTrack = soundTrackAsset.tracks(withMediaType: .audio).first!do {try audioTrack?.insertTimeRange(audioTimeRange, of: soundTrackAssetTrack, at: cursorTime)} catch {print("Error inserting time range: \(error)")}
- 首先我们创建了3个AVAsset资源,当然这里面是模拟创建的,其中前2个表示视频,第3个表示音频。
- 定义了资源的插入时间点。
- 定义每个视频片段资源的插入时长。
- 提取第1个视频资源的视频轨道,默认视频资源只有一个视频轨道,插入到组合的视频轨道。
- 调整下一个视频资源的插入时间为上一个视频资源的结束时间点。
- 同样获取第2个视频资源的视频轨道,插入到组合的视频轨道。
- 调整插入时间为0,并设置音频的时长。
- 提取音频资源的音频轨道并插入到组合的音频轨道中。
这样我们的组合就构建完成了:
使用示例
下面我们将着色创建一个视频编辑的应用程序,接下来的博客也将围绕这个程序不断的添加和完善视频编辑的功能。
项目介绍
应用程序将包含两个不同的部分,一个是视频播放器,我们只需在之前博客的视频播放器中稍作改动,一个是可以选择媒体和允许媒体排列组合的视频编辑部分,重点会放在视频编辑的部分。
播放器
播放器和视频播放相关博客的播放器大致相同,只是原来传入播放器的是视频地址,而现在传入的需要是一个完整的AVPlayerItem。因此需要重写了init方法,并且添加另一个用于替换当前播放AVPlayerItem的方法。
init方法:
override init() {super.init()self.player = AVPlayer(playerItem: playerItem)if let player = player {playerView = PHPlayerView(player: player)}addObserverForPlayerItem()}/// 自定义初始化方法////// - Parameters:/// - playerItem: AVPlayerIteminit(playerItem: AVPlayerItem? = nil) {super.init()self.playerItem = playerItemself.player = AVPlayer(playerItem: playerItem)if let player = player {playerView = PHPlayerView(player: player)}addObserverForPlayerItem()}
替换当前AVPlayerItem方法:
/// AVPlayer的同名方法,替换当前播的资源////// - Parameters:/// - playerItem: AVPlayerItemfunc replaceCurrentItem(playerItem:AVPlayerItem?) {guard let player = self.player else { return }self.playerItem = playerItemplayer.replaceCurrentItem(with: playerItem)addObserverForPlayerItem()}/// 为AVPlayerItem添加监听func addObserverForPlayerItem() {guard let playerItem = playerItem else { return }playerItem.addObserver(self, forKeyPath: status_keypath, context: &playerItemContext)}
另外我们将播放进度的监听由原来的0.5改为了1/60秒,因为我们需要使用它来同步动画,而不仅仅是显示当前时间。
/// 监听播放进度func addPlayerItemTimeObserver() {guard let player = player else { return }let interval = CMTimeMakeWithSeconds(1/60.0, preferredTimescale: Int32(NSEC_PER_SEC))let queue = DispatchQueue.maintimeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: queue, using: {[weak self] time inguard let self = self else { return }guard let playerItem = self.playerItem else { return }guard let delegate = self.delegate else { return }let currentTime = CMTimeGetSeconds(time)let duration = CMTimeGetSeconds(playerItem.duration)delegate.setCuttentTime(time: currentTime, duration: duration)})}
编辑器
编辑器由两部分构成,媒体资源选择器,和媒体资源编辑区域。
博客的示例项目中,我们只获取了视频媒体资源,音频媒体资源的做法与视频完全相同,只是传入的mediaType为audio。
媒体资源选择器为一个简单的列表,点击加号后会根据所选择的媒体资源创建一个PHMediaItem,PHMediaItem是一个基类,它的子类又分为PHVideoItem和PHAudioItem,后续的功能我们也许会用到PHAudioItem,但目前我们只需要使用PHVideoItem即可。
媒体资源编辑器就是页面除播放器以外的下半部分,有显示媒体选择器的按钮,和控制播放器播放和暂停的按钮,以及一个显示媒体剪辑状态的时间轴区域。
创建组合
我们的核心任务就是通过页面上的一些操作来创建一个媒体组合,首先声明一个PHComposition协议,协议中定义了两个方法分别用来生成组合的可播放版本和可导出版本。
import UIKit
import AVFoundationprotocol PHComposition {/// 协议方法-生成AVPlayerItem////// - Returns: 返回一个可播放的AVPlayerItemfunc makePlayerItem() -> AVPlayerItem?/// 协议方法-生成AVAssetExportSession////// - Returns: 返回一个可导出的AVAssetExportSessionfunc makeAssetExportSession() -> AVAssetExportSession?}
创建一个遵循PHComposition协议的类,并提供协议方法的实现。
// 负责创建 视频的可播放资源和可导出资源import UIKit
import AVFoundationclass PHBaseComposition: NSObject,PHComposition {//只读compositionprivate var compostion:AVComposition?//自定义初始化init(compostion: AVComposition? = nil) {self.compostion = compostion}//MARK: PHComposition - 生成 AVPlayerItemfunc makePlayerItem() -> AVPlayerItem? {if let compostion = compostion {let playerItem = AVPlayerItem(asset: compostion)return playerItem}return nil}//MARK: PHComposition - 生成 AVAssetExportSessionfunc makeAssetExportSession() -> AVAssetExportSession? {return nil}
}
创建一个组合的构建器,同样我们创建一个协议,负责来创建遵循PHComposition协议的对象。
import UIKitprotocol PHCompositionBuilder {/// 协议方法-生成一个遵循PHComposition协议的对象////// - Returns: 返回一个最新PHComposition协议的对象func buildComposition() -> PHComposition?
}
这个协议的具体方法由PHBaseCompositionBuilder来实现,代码如下。
import UIKit
import AVFoundationclass PHBaseCompositionBuilder: NSObject,PHCompositionBuilder {/// 时间线var timeLine:PHTimeLine!/// compositionprivate var composition = AVMutableComposition()init(timeLine: PHTimeLine!) {self.timeLine = timeLine}//MARK: PHCompositionBuilder - 生成 PHCompositionfunc buildComposition() -> PHComposition? {addCompositionTrack(mediaType: .video, mediaItems: timeLine.videoItmes)return PHBaseComposition(compostion: self.composition)}/// 私有方法-添加媒体资源轨道/// - Parameters:/// - mediaType: 媒体类型/// - mediaItems: 媒体媒体资源数组/// - Returns: 返回一个可播放的AVPlayerItemprivate func addCompositionTrack(mediaType:AVMediaType,mediaItems:[PHMediaItem]?) {if PHIsEmpty(array: mediaItems) {return}let trackID = kCMPersistentTrackID_Invalidguard let compositionTrack = composition.addMutableTrack(withMediaType: mediaType, preferredTrackID: trackID) else { return }//设置起始时间var cursorTime = CMTime.zeroguard let mediaItems = mediaItems else { return }for item in mediaItems {//这里默认时间都是从0开始guard let asset = item.asset else { continue }guard let assetTrack = asset.tracks(withMediaType: mediaType).first else { continue }do {try compositionTrack.insertTimeRange(item.timeRange, of: assetTrack, at: cursorTime)} catch {print("addCompositionTrack error")}cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)}}
}
- PHBaseCompositionBuilder在初始化的时候会默认创建一个AVMutableComposition对象用于媒体编辑操作。
- 获取timeLine对象中的所有视频资源,调用addCompositionTrack方法进行拼接。
- addCompositionTrack方法中首先判断了传入的资源数组是否为空。
- 当媒体资源数组不为空的时候,从composition中获取对应的媒体轨道。
- 设置起始时间,遍历媒体资源数组,从每个资源中获取对应的媒体轨道并添加到组合媒体轨道中。
- 修改下一个媒体资源的插入起始时间。
实现播放组合媒体
选择媒体资源
点击页面上的加号按钮,显示媒体选择列表,点击列表后会将选择的媒体资源创建为PHMediaItem并添加到当前的timeLine对应的资源数组下。
//MARK: 显示选择视频视图@objc func showItemPickerView() {let resourcePickerView = PHResourcePickerView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height * 0.5, width: 150.0, height: 200.0))resourcePickerView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1.0)resourcePickerView.layer.masksToBounds = trueresourcePickerView.layer.cornerRadius = 5.0resourcePickerView.layer.borderColor = UIColor.white.cgColorresourcePickerView.layer.borderWidth = 1.0resourcePickerView.showDialog()resourcePickerView.addMediaItemBlock = { [weak self] mediaItem inguard let self = self else { return }if var videoItmes = timeLine.videoItmes {videoItmes.append(mediaItem as! PHVideoItem)timeLine.videoItmes = videoItmes} else {var videoItmes = [PHVideoItem]()videoItmes.append(mediaItem as! PHVideoItem)timeLine.videoItmes = videoItmes}self.collectionView?.reloadData()self.needReplay = true}}
点击播放按钮
点击播放按钮后判断是否有可播放资源,再进行播放。播放分为两种情况,从头开始播放和暂停后的继续播放。
//MARK: 播放按钮点击@objc func playerButtonOnlick(button:UIButton) {if PHIsEmpty(array: timeLine.videoItmes) {playerButton.isSelected = falsereturn}button.isSelected = !button.isSelected//回调guard let delegate = self.delegate else { return }if button.isSelected {if needReplay {player()needReplay = false} else {delegate.play()}} else {delegate.pause()}}func player() {guard let delegate = self.delegate else { return }let compositionBuilder = PHBaseCompositionBuilder(timeLine: timeLine)let composition = compositionBuilder.buildComposition()let playerItem = composition?.makePlayerItem()delegate.replaceCurrentItem(playerItem: playerItem)}
同步播放进度
PHEditorView视频编辑器遵循了PHControlDelegate协议,这里我们只关注setCuttentTime和playbackComplete方法。
setCuttentTime方法用来同步编辑器时间轴的进度。
playbackComplete用来同步播放按钮的状态。
extension PHEditorView:PHControlDelegate{func playpause(currentTime: TimeInterval) {}func playstart(duration: TimeInterval) {}func setCuttentTime(time: TimeInterval, duration: TimeInterval) {let origin_offsetX = -UIScreen.main.bounds.width * 0.5self.collectionView?.contentOffset = CGPointMake(origin_offsetX + time * item_size.width, 0.0)}func playbackComplete() {self.playerButton.isSelected = false}}
结语
在示例项目中,我们仅仅涉及了视频媒体资源,并默认这些资源都是单轨道的。然而,在实际的应用开发中,我们可能会面对更加复杂的情况,涉及到多种类型的媒体资源,以及多轨道的组合。iOS AVFoundation框架为我们提供了强大的工具和灵活的接口,让我们能够处理各种各样的媒体资源,并将它们巧妙地组合成为精彩纷呈的作品。通过深入理解和灵活运用AVFoundation框架,我们可以实现更加复杂和令人惊叹的音视频编辑应用,为用户带来全新的体验和享受。在今后的开发过程中,让我们继续探索和挖掘AVFoundation框架的潜力,创造出更加优秀和创新的音视频编辑应用!
项目地址:PHEditorPlayer: AV Foundation 音视频编辑
相关文章:
二.音视频编辑-媒体组合-播放
引言 当涉及到音视频编辑时,媒体资源的提取和组合是至关重要的环节。在iOS平台上,AVFoundation框架提供了丰富而强大的功能,使得媒体资源的操作变得轻松而高效。从原始的媒体中提取片段,然后将它们巧妙地组合成一个完整的作品&am…...
前端安全-面试题(2024)
1. 面试总结话术: 前端常见的安全问题主要包括以下几种: 跨站脚本攻击(XSS):攻击者通过在目标网站注入恶意脚本,当用户访问网站时,恶意脚本会被执行,从而窃取用户信息或进行其他恶意操作。这种攻击通常利用表单提交、URL参数等方式注入脚本。存储型 xss 恶意代码存在数…...
CVE-2022-29405 Apache Archiva任意用户密码重置漏洞分析
Apache Archiva是一套可扩展的Artifact Repository管理系统。它能够与Maven,Continuum和ANT等构建工具完美结合。Archiva提供的功能包括:远程Repository代理,基于角色的安全访问管理,Artifact分发、维护、查询,生成使用…...
ssm框架配置文件例子
emmm。。。。 就是说,正常ssm的配置文件长啥样? 就最基础的? 贴一下,备忘吧。 第一个:applicationContext.xml <beans xmlns"http://www.springframework.org/schema/beans"xmlns:context"http…...
maven构建项目报错:Failure to find com.microsoft.sqlserver:sqljdbc4:jar:4.0 in
背景 今天在项目里面查询sqlserver的数据库的时候,本地maven中引入依赖: <dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>sqljdbc4</artifactId><version>4.0</version></dependenc…...
已解决rabbitmq AMQPConnectionClosedException:管道破裂或连接关闭异常的正确解决方法,亲测有效!!!
已解决rabbitmq AMQPConnectionClosedException:管道破裂或连接关闭异常的正确解决方法,亲测有效!!! 目录 一、问题分析 二、报错原因 三、解决思路 四、解决方法 五、总结 博主v:XiaoMing_Java 一、…...
Excel 隔几行批量插入空白行
例如如下表格,每隔6行插入一行数据: 1)第7个单元格输入1 2)选中6个单元格,然后双击填充数据: 3)F5 找到常量 Ctrlshift 复制插入的数据,然后选中数据 按F5,定位到空值...
2024年04月在线IDE流行度最新排名
点击查看最新在线IDE流行度最新排名(每月更新) 2024年04月在线IDE流行度最新排名 TOP 在线IDE排名是通过分析在线ide名称在谷歌上被搜索的频率而创建的 在线IDE被搜索的次数越多,人们就会认为它越受欢迎。原始数据来自谷歌Trends 如果您相…...
如何通过Elasticsearch实现搜索的关键词达到高亮的效果
高亮 首先介绍一下什么是搜索的关键词达到高亮的效果,如图所示 当在百度里面搜索elasticsearch的时候,可以看到出现的搜索结果里面elasticsearch这个关键词明显与其他的条文不一样,用红颜色凸显了“高亮效果”。当我们想要在自己的项目里面…...
真实sql注入以及小xss--BurpSuite联动sqlmap篇
前几天漏洞检测的时候无意发现一个sql注入 首先我先去网站的robots.txt去看了看无意间发现很多资产 而我意外发现admin就是后台 之后我通过基础的万能账号密码测试or ‘1‘’1也根本没有效果 而当我注入列的时候情况出现了 出现了报错,有报错必有注入点 因此我…...
Java类和对象练习题
练习一 下面代码的运行结果是() public static void main(String[] args){String s;System.out.println("s"s);} 解析:本题中的代码不能编译通过,因为在Java当中局部变量必须先初始化,后使用。所以此处编译不…...
Qt 实现简易的视频播放器,功能选择视频,播放,暂停,前进,后退,进度条拖拉,视频时长显示
1.效果图 2.代码实现 2.1 .pro文件 QT core gui multimedia multimediawidgets 2.2 .h文件 #ifndef VIDEOPLAYING_H #define VIDEOPLAYING_H#include <QWidget> #include<QFileDialog>#include<QMediaPlayer> #include<QMediaRecorder> #in…...
vue基础教程(6)——构建项目级登录页
同学们可以私信我加入学习群! 正文开始 前言一、创建首页二、登录页代码讲解三、对应的vue知识点:四、附件-各文件代码总结 前言 前面我们已经把vue自带的页面删除,也搭建了最简单的router路由,下面就可以真正开发我们自己的项目…...
C++宝强越狱1.0.6版本
没啥好说的,更新了一关,上代码 #include"bits/stdc.h" #include"Windows.h" #define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0) using namespace std; int w3,s3,a3,d3; bool nfalse,iptrue,mfals…...
构建高可用性数据库架构:深入探索Oracle Active Data Guard(ADG)
随着企业数据规模的不断增长和业务的复杂化,数据库的高可用性和可靠性变得尤为重要。Oracle Active Data Guard(ADG)作为Oracle数据库提供的一种高可用性解决方案,在实时备份和灾难恢复方面发挥着重要作用。本文将深入探讨ADG的原…...
记录-rosbag的处理
https://blog.csdn.net/qq_39607707/article/details/123716925 https://blog.csdn.net/weixin_51060040/article/details/126612496...
用Wireshark解码H.264
H264,你不知道的小技巧-腾讯云开发者社区-腾讯云 这篇文章写的非常好 这里仅做几点补充 init.lua内容: -- Set enable_lua to false to disable Lua support. enable_lua trueif not enable_lua thenreturn end-- If false and Wireshark was start…...
Flink中几个关键问题总结
硬核!八张图搞懂 Flink 端到端精准一次处理语义 Exactly-once(深入原理,建议收藏) Flink可靠性的基石-checkpoint机制详细解析 硬核!一文学完Flink流计算常用算子(Flink算子大全)...
华为配置ARP安全综合功能实验
华为配置ARP安全综合功能实验 组网图形 图1 配置ARP安全功能组网图 ARP安全简介配置注意事项组网需求配置思路操作步骤配置文件 ARP安全简介 ARP(Address Resolution Protocol)安全是针对ARP攻击的一种安全特性,它通过一系列对ARP表项学…...
new mars3d.layer.XyzLayer({的rectangle瓦片数据的矩形区域范围说明
new mars3d.layer.XyzLayer({的rectangle瓦片数据的矩形区域范围说明 2.这个xyz图层的矩形区域范围rectangle从图层文件中无法获取,但是看图层文件可以知道这个是12-21级的数据。 3.一般这个图层数据文件服务会有提供相应的rectangle范围,在服务的xml文…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...
