大师学SwiftUI第18章Part3 - 自定义视频播放器

视频

录制和播放视频对用户来说和拍照、显示图片一样重要。和图片一样,Apple框架中内置了播放视频和创建自定义播放器的工具。

视频播放器

SwiftUI定义了​​VideoPlayer​​视图用于播放视频。该视图提供了所有用于播放、停止、前进和后退的控件。视图包含如下初始化方法。

  • VideoPlayer(player: AVPlayer?, videoOverlay: Closure):该初始化方法创建视频播放器来播放通过参数提供的视频。​​player​​​参数是负责播放的对象,​​videoOverlay​​参数提供希望展示在视频上方的视图。

​VideoPlayer​​​视图展示用户控制视频的界面,但视频由​​AVPlayer​​类的对象播放。该类包含如下初始化方法。

  • AVPlayer(url: URL):该初始化方法创建一个​​AVPlayer​​​对象来播放​​url​​参数所指向URL的媒体。

​AVPlayer​​类还提供通过程序控制视频的属性和方法。

  • volume:该属性设置或返回决定播放器音量的值。值为0.0到1.0之间的​​Float​​类型值。
  • isMuted:该属性是一个布尔值,决定播放器的音频是否为静音。
  • rate:该属性设置或返回一个​​Float​​值,决定所播放媒体的速度。0.0表示暂停视频,1.0设为常速。
  • play():该方法开启播放。
  • pause():该方法暂停播放。
  • addPeriodicTimeObserver(forInterval: CMTime, queue: DispatchQueue?, using: Closure):该方法添加一个观察者,每隔一定的时间执行闭包的内容。​​forInterval​​​参数决定执行的间隔,​​queue​​​参数为闭包所处的队列(推荐用主线程),​​using​​​参数是希望执行闭包。闭包接收​​CMTime​​类型的值,为闭包调用的时间。

​VideoPlayer​​​视图需要有​​AVPlayer​​​对象来播放视频,该对象对过URL加载视频。如果希望播放线上的视频,只需要URL,但如果视频由应用提供,则需要通过包来获取(参见第10章中的Bundle)。在以下的模型中,我们在项目中添加了一个视频videotrees.mp4,通过​​Bundle​​​对象获取指向该文件的URL,并用该值创建一个​​AVPlayer​​对象。

示例18-19:准备待播放的视频

import SwiftUI
import Observation
import AVKit@Observable class ApplicationData {var player: AVPlayer!init() {let bundle = Bundle.mainif let videoURL = bundle.url(forResource: "videotrees", withExtension: "mp4") {player = AVPlayer(url: videoURL)}}
}

​VideoPlayer​​​视图和​​AVPlayer​​​类来自AVKit框架。导入该框架后,我们获取到videotrees.mp4视频的URL,创建​​AVPlayer​​​对象并将其存储到可观测属性中,以供视图使用。在视图中,我们需要检测该属性并在视频准备就绪后显示​​VideoPlayer​​视图。

示例18-20:播放视频

import SwiftUI
import AVKitstruct ContentView: View {@Environment(ApplicationData.self) private var appDatavar body: some View {if appData.player != nil {VideoPlayer(player: appData.player).ignoresSafeArea()} else {Text("Video not available")}}
}

图18-9:标准视频播放器

图18-9:标准视频播放器

✍️跟我一起做:创建一个多平台项目。下载videotrees.mp4并添加至项目中(别忘了在弹窗中选择target)。使用示例18-19中的代码创建一个Swift模型文件​​ApplicationData.swift​​。使用示例18-20中的代码更新​​ContentView​​​视图。还要将​​ApplicationData​​对象注入应用和预览的环境中(参见第7章示例7-4)。运行应用。点击播放按钮播放视频。

上例中,视频需要由用户点击播放按钮才开始播放。但我们可以实现​​AVPlayer​​属性和方法来通过程序控制视频。例如,以下示例在视图加载完后就开始播放视频。

示例18-21:自动播放视频

struct ContentView: View {@Environment(ApplicationData.self) private var appDatavar body: some View {if appData.player != nil {VideoPlayer(player: appData.player).onAppear {appData.player.play()}.ignoresSafeArea()} else {Text("Video not available")}}
}

​VideoPlayer​​视图初始化方法还可以包含一个参数,接收闭包来在视频上添加浮层。下例中,实现的初始化方法在视频的顶部添加标题。

示例18-22:在视频上展示视图

struct ContentView: View {@Environment(ApplicationData.self) private var appDatavar body: some View {if appData.player != nil {VideoPlayer(player: appData.player, videoOverlay: {VStack {Text("Title: Trees at the park").font(.title).padding([.top, .bottom], 8).padding([.leading, .trailing], 16).foregroundColor(.black).background(.ultraThinMaterial).cornerRadius(10).padding(.top, 8)Spacer()}}).ignoresSafeArea()} else {Text("Video not available")}}
}

闭包返回中的视频位于视频之上和控件之下,因此无法接收用户的输入,但可以使用它来提供额外的信息,就像本例中这样。结果如下所示。

图18-10:浮层视图

图18-10:浮层视图

自定义视频播放器

除了让​​VideoPlayer​​视图正常工作的代码外,AVFoundation框架还提供了创建播放媒体独立组件的功能。有一个负责资源(视频或音频)的类,一个负责将媒体资源发送给播放器的类,一个播放媒体的类以及一个负责在屏幕上显示媒体的类。图18-11描述了这一结构。

图18-11:播放媒体的系统

图18-11:播放媒体的系统

待播放的媒体以资源形式提供。资源以一个或多个媒体轨道组成,包括视频、音频和字幕等。AVFoundation框架定义了一个​​AVAsset​​类来加载资源。该类包含如下初始化方法。

  • AVURLAsset(url: URL):这个初始化方法使用​​url​​​参数指定位置的资源创建​​AVURLAsset​​对象。参数是一个URL结构体,包含本地或远程资源的位置。

资源包含有静态信息,在播放后无法管理自身的状态。框架定义了​​AVPlayerItem​​类来控制资源。通过此类我们可以引用资源并管理其时间轴。该类中包含多个初始化方法。以下是最常用的一个。

  • AVPlayerItem(asset: AVAsset):本初始化方法创建一个表示​​asset​​​参数所指定的资源的​​AVPlayerItem​​对象。

​AVPlayerItem​​类还包含一些控制资源状态的属性和方法。以下是最常用的那些。

  • status:该属性返回表示播放项状态的值。这是一个位于​​AVPlayerItem​​​类中​​Status​​​枚举。值有​​unknown​​​、​​readyToPlay​​​和​​failed​​。
  • duration:该属性返回表示播放项时长的值。它是一个​​CMTime​​类型的结构体。
  • currentTime():此方法返回播放项当前时间的​​CMTime​​值。
  • seek(to: CMTime):这一异步方法将播放游标移动到​​to​​参数所指定的时间,返回一个寻址操作是否完成的布尔值。

​AVPlayerItem​​​对象管理播放所需的信息,但不会播放媒体,这是由​​AVPlayer​​​类的实例来处理的。它是稍早我们在​​VideoPlayer​​​视图中用于加载视频相同的类。该类包含如下通过​​AVPlayerItem​​对象创建播放器的初始化方法。

  • AVPlayer(playerItem: AVPlayerItem?):这一初始化方法创建一个​​AVPlayer​​​对象播放​​playerItem​​参数所表示的媒体资源。

系统所需的最后一个对象负责展示媒体资源。它是​​CALayer​​​的子类​​AVPlayerLayer​​,提供了在屏幕上绘制视频帧所需要的代码。该类包含如下创建和配置播放层的初始化方法和属性。

  • AVPlayerLayer(player: AVPlayer):本初始化方法创建一个​​AVPlayerLayer​​​对象,关联​​player​​参数所指定的播放器。
  • videoGravity:此属性定义了如何将视频调整为预览层的大小。它是一个​​AVLayerVideoGravity​​​结构体,包含类型属性​​resize​​​、​​resizeAspect​​​和​​resizeAspectFill​​。

这些类一起定义了用于播放媒体的系统,但还要有方法来控制时间。因浮点数的精度不适合于播放媒体资源,框架还通过旧框架的Core Media实现了​​CMTime​​​结构体。这一结构体包含了很多以分数表示时间的值。最重要的两个是​​value​​​和​​timescale​​​,分别表示分子和分母。例如,想要创建表示0.5秒的​​CMTime​​结构体时,可以指定分子为1、分母为2(1除以2得0.5)。该类包含一些创建这些值的初始化方法和类型属性。以下是最常使用的。

  • CMTime(value: CMTimeValue, timescale: CMTimeScale):此初始化方法通过​​value​​​和​​timescale​​​所指定的值创建一个​​CMTime​​​结构体。参数分别为整型​​Int64​​​主​​Int32​​。
  • CMTime(seconds: Double, preferredTimescale: CMTimeScale):此初始化方法通过表示秒数的浮点值创建一个​​CMTime​​​结构体。​​seconds​​​参数为赋给结构体的秒数,​​preferredTimescale​​参数为希望使用的单位。值为1时保持为第一个参数的秒数。
  • zero:该类型属性返回值为0的​​CMTime​​结构体。

​CMTime​​结构体还包含一些设置和获取值的属性。最常用的如下。

  • seconds:该属性以秒数返回​​CMTime​​​结构体的时间。类型为​​Double​​。
  • value:该属性返回​​CMTime​​结构体的值。
  • timescale:该属性返回​​CMTime​​结构体的时间单位。

要自定义视频播放器,我们必须加载资源(​​AVURLAsset​​​),创建一个管理资源的子项(​​AVPlayerItem​​​),将子项添加至播放器(​​AVPlayer​​​),将播放器关联至屏幕上媒体的显示层(​​AVPlayerLayer​​)。

就像前面用于显示来自相机的视频的预览层,我们需要将​​UIView​​​对象提供的显示层转化为预览层(本例中​​CALayer​​​需要转换为​​AVPlayerLayer​​对象)。以下是本例需要实现的表现视图。

示例18-23:构建自定义播放器

import SwiftUI
import AVFoundationclass CustomPlayerView: UIView {override class var layerClass: AnyClass {return AVPlayerLayer.self}
}struct PlayerView: UIViewRepresentable {var view = CustomPlayerView()func makeUIView(context: Context) -> UIView {return view}func updateUIView(_ uiView: UIViewType, context: Context) {}
}

有了表现视图,下一步就是构建视频播放器,然后在就绪后调用​​player()​​方法播放视频。

示例18-24:构建自定义视频播放器

import SwiftUI
import Observation
import AVFoundationclass ViewData: NSObject {var playerItem: AVPlayerItem?var player: AVPlayer?var playerLayer: AVPlayerLayer?var playerObservation: NSKeyValueObservation?func setObserver() {playerObservation = playerItem?.observe(\.status, options: .new, changeHandler: { item, value inif item.status == .readyToPlay {self.player?.play()}})}
}@Observable class ApplicationData {@ObservationIgnored var customVideoView: PlayerView!@ObservationIgnored var viewData: ViewDatainit() {customVideoView = PlayerView()viewData = ViewData()let bundle = Bundle.mainlet videoURL = bundle.url(forResource: "videotrees", withExtension: "mp4")let asset = AVURLAsset(url: videoURL!)viewData.playerItem = AVPlayerItem(asset: asset)viewData.player = AVPlayer(playerItem: viewData.playerItem)viewData.playerLayer = customVideoView.view.layer as? AVPlayerLayerviewData.playerLayer?.player = viewData.playerviewData.setObserver()}
}

视频并非立马可见,需要进行加载和做好播放准备,因而不能马上播放,需等待其就绪。媒体的状态由​​AVPlayerItem​​​的​​status​​​属性进行上报。因此需要监测该属性的值,公在其值等于​​readyToPlay​​​时开始播放。这就要使用到观察过生日和。因此,在定义三个属性后,我们需要存储播放项、播放器和播放层,我们定义了一个存储观察者的属性,调用​​AVPlayerItem​​​对象的​​observer()​​​方法来跟踪​​status​​​属性。在当前状态为​​readyToPlay​​时播放视频。

为配置视频播放器,我们从bundle中加载视频、创建播放器结构体、将​​UIView​​​层转换为​​AVPlayerLayer​​​,将其赋值给​​player​​。因所有内容都在模型中进行了准备,界面只需要在展示视图中进行显示。视频填满屏幕、适配屏幕的朝向并在加载视图后进行播放。

示例18-25:显示视频

struct ContentView: View {@Environment(ApplicationData.self) private var appDatavar body: some View {appData.customVideoView.ignoresSafeArea()}
}

✍️跟我一起做:创建一个多平台项目。下载videotrees.mp4,添加至项目中(记住要勾选Add to Target选项)。使用示例18-23中的代码创建​​CustomPlayerView.swift​​​文件,使用示例18-24中的创建模型文件​​ApplicationData.swift​​​。再用示例18-25中的代码更新​​ContentView​​视图。运行应用,视频应该会在应用启动后立即播放。

上例中进行了视频的播放,但没为用户提供任何控件工具。​​AVPlayer​​类包含有播放、暂停和检查媒体状态的方法,但需要我们来创建界面。下例中我们会创建一个带有按钮和进度条的界面,这样用户可以播放、暂停并查看视频的进度。

图18-12:自定义视频播放器的控件

图18-12:自定义视频播放器的控件

如何控制流程以及对界面进行响应取决于应用的要求。例如,我们决定定义两个状态,一个表示视频是否在播放,另一个表示进度条的位置。以下是对模型所做的修改,让用户可以播放、暂停视频以及拖动进度条。

示例18-26:准备视频播放器

import SwiftUI
import Observation
import AVFoundationclass ViewData: NSObject {var playerItem: AVPlayerItem?var player: AVPlayer?var playerLayer: AVPlayerLayer?
}@Observable class ApplicationData {var playing: Bool = falsevar progress: CGFloat = 0@ObservationIgnored var customVideoView: PlayerView!@ObservationIgnored var viewData: ViewDatainit() {customVideoView = PlayerView()viewData = ViewData()let bundle = Bundle.mainlet videoURL = bundle.url(forResource: "videotrees", withExtension: "mp4")let asset = AVURLAsset(url: videoURL!)viewData.playerItem = AVPlayerItem(asset: asset)viewData.player = AVPlayer(playerItem: viewData.playerItem)viewData.playerLayer = customVideoView.view.layer as? AVPlayerLayerviewData.playerLayer?.player = viewData.playerlet interval = CMTime(value: 1, timescale: 2)viewData.player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { time inif let duration = self.viewData.playerItem?.duration {let position = time.seconds / duration.secondsself.progress = CGFloat(position)}})}func playVideo() {if viewData.playerItem?.status == .readyToPlay {if playing {viewData.player?.pause()playing = false} else {viewData.player?.play()playing = true}}}
}

本例中,我们添加了一个​​playVideo()​​​方法,在用户点击Play按钮时执行。该方法检测是否可以播放媒体,然后根据​​playing​​​属性的值执行操作。如果视频在播放就暂停,如果在暂停就播放。​​playing​​属性的值会进行更新来反映新的状态。

要计算进度条的长度,必须要实现一个观察者。但不是像之前所实现的KVO观察者。常规的观察者不够快,所以AVFoundation框架自带了一个​​addPeriodicTimeObserver()​​​方法创建提供更精准响应的观察者。该方法需要一个​​CMTime​​​值来指定执行任务的频率、一个主队列指针以及一个带每次触发观察者执行代码的闭包。本例中,我们创建一个表示0.5秒时长的​​CMTime​​​值,然后使用它调用​​addPeriodicTimeObserver()​​方法来注册观察者。之后,传递给观察者的闭包在播放期间每0.5秒执行一次。在闭包中,我们获取到了当前时间以及视频时长秒数,通过将秒数转换成0.0到1.0之间的值来计算进度,稍后可转化成点数在屏幕上显示进度条。

注意:​​addPeriodicTimeObserver()​​​方法无法用于Swift的并发。而是需要将线程定义在​​DispatchQueue​​​对象中。这是由Dispatch框架定义的老类,用于创建异步任务。该类包含一个类型属性​​main​​,定义一个主队列任务(Main Actor),这正是确保赋给这一方法的闭包在主线程中运行的方式。

播放器已就绪,是时修改定义界面了。在这个场景中,我们需要在​​ZStack​​中展示表现视图,这样可以在上层显示工具栏(参见图18-12)。

示例18-27:播放及暂停视频

struct ContentView: View {@Environment(ApplicationData.self) private var appDatavar body: some View {ZStack {appData.customVideoView.ignoresSafeArea()VStack {Spacer()HStack {Button(appData.playing ? "Pause" : "Play") {appData.playVideo()}.frame(width: 70).foregroundColor(.black)GeometryReader { geometry inHStack {Rectangle().fill(Color(red: 0, green: 0.4, blue: 0.8, opacity: 0.8)).frame(width: geometry.size.width * appData.progress, height: 20)Spacer()}}.padding(.top, 15)}.padding([.leading, .trailing]).frame(height: 50).background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 0.8))}}}
}

工具栏包含一个按钮和一个表示进度条的​​Rectangle​​​视图。按钮的标签取决于​​playing​​属性的值。在视频播放时显示Pause,暂停时显示Play。为计算表示进度条的​​Rectangle​​​视图的长度,我们嵌入了​​GeometryReader​​​视图,然后用其宽度乘上​​progress​​属性。因该属性包含一个0.0到1.0之间的值,这一运算返回一个设置进度条宽度的值,在屏幕上显示出进度。

✍️跟我一起做:使用示例18-26中的代码更新模型,用示例18-27中的代码更新​​ContentView​​视图。运行程序,就会看到图18-12中所示的视频播放器。

通过​​addPeriodicTimeObserver()​​​方法添加的观察者不是获取播放器的信息的唯一方式。​​AVPlayerItem​​​类还定义了一些通知用于报告媒体播放期间发生的事件。例如,我们可以监听​​AVPlayerItemDidPlayToEndTime​​​通知来了解视频何时停止播放。为此,我们需要在模型中定义一个方法监听并响应该通知,并添加一个任务在展示视图创建时调用该方法。以下是我们需要在​​ApplicationData​​类的初始化方法添加的任务。

示例18-28:执行异步方法监测视频结束

Task(priority: .background) {await rewindVideo()}

在​​rewindVideo()​​​方法中,我们必须监听​​AVPlayerItemDidPlayToEndTime​​​通知,并准备再次播放视频。为此,​​AVPlayerItem​​​类提供了​​seek()​​​方法。该方法将播放进度移到参数所指定的时间,并在处理完成后执行一个闭包。本例中我们将使用值为0的​​CMTime​​​将播放器移到视频开头,然后重置​​playing​​​和​​progress​​属性允许用户重新播放视频。

示例18-29:重新播放视频

func rewindVideo() async {let center = NotificationCenter.defaultlet name = NSNotification.Name.AVPlayerItemDidPlayToEndTimefor await _ in center.notifications(named: name, object: nil) {if let finished = await viewData.playerItem?.seek(to: CMTime.zero), finished {await MainActor.run {playing = falseprogress = 0}}}}

✍️跟我一起做:将示例18-28中的任务添加到​​ApplicationData​​初始化方法的最后。将示例18-29中的方法添加到​​ApplicationData​​类的最后。运行程序。点击播放,等待视频结束。播放器应该会重置,可以再次播放视频。

如果希望按顺序播放多个视频,我们可以使用​​AVPlayerItemDidPlayToEndTime​​​通知将新资源赋值给​​AVPlayer​​​对象,但框架提供了​​AVPlayer​​​类的子类​​AVQueuePlayer​​​,专门上用于管理视频列表。该类通过​​AVPlayerItem​​对象数组创建一个播放列表。以下为初始化方法和其中的一些方法。

  • AVQueuePlayer(items: [AVPlayerItem]):该方法通过​​items​​参数指定的播放项创建一个播放列表。
  • advanceToNextItem():该方法将播放内容递进至列表中的下一项。
  • insert(AVPlayerItem, after: AVPlayerItem?):该方法在列表中插入一个新项。
  • remove(AVPlayerItem):该方法从列表中删除一项。

​AVQueuePlayer​​​对象替换用于展示媒体资源的​​AVPlayer​​​对象。播放视频序列我闪只需要每个视频创建一个​​AVPlayerItem​​​对象,以及将我们一直使用的​​AVPlayer​​​对象替换为​​AVQueuePlayer​​对象,如下例所示。

示例18-30:播放视频列表

import SwiftUI
import Observation
import AVFoundationclass ViewData: NSObject {var playerItem1: AVPlayerItem!var playerItem2: AVPlayerItem!var player: AVQueuePlayer!var playerLayer: AVPlayerLayer?var playerObservation: NSKeyValueObservation?func setObserver() {playerObservation = playerItem1.observe(\.status, options: .new, changeHandler: { item, value inif item.status == .readyToPlay {self.player.play()}})}
}@Observable class ApplicationData {var playing: Bool = falsevar progress: CGFloat = 0@ObservationIgnored var customVideoView: PlayerView!@ObservationIgnored var viewData: ViewDatainit() {customVideoView = PlayerView()viewData = ViewData()let bundle = Bundle.mainlet videoURL1 = bundle.url(forResource: "videotrees", withExtension: "mp4")let videoURL2 = bundle.url(forResource: "videobeaches", withExtension: "mp4")let asset1 = AVURLAsset(url: videoURL1!)let asset2 = AVURLAsset(url: videoURL2!)viewData.playerItem1 = AVPlayerItem(asset: asset1)viewData.playerItem2 = AVPlayerItem(asset: asset2)viewData.player = AVQueuePlayer(items: [viewData.playerItem1, viewData.playerItem2])viewData.playerLayer = customVideoView.view.layer as? AVPlayerLayerviewData.playerLayer?.player = viewData.playerviewData.setObserver()}
}

本例中,我们使用的是示例18-25中的​​ContentView​​视图。代码中加载了两个视频,videotrees.mp4videobeaches.mp4,然后创建两个​​AVURLAsset​​​对象以及两个用于展示它们的​​AVPlayerItem​​​对象。接着定义​​AVQueuePlayer​​​对象来按顺序播放这两个视频。注意因为在本例中我们使用的界面没带播放按钮,我们对第一个视频添加了一个观察者,在准备就绪后调用​​play()​​方法。

✍️跟我一起做:使用示例18-30中的代码更新ApplicationData.swift文件。结合示例18-25中的​​ContentView​​视图使用。下载videotrees.mp4videobeaches.mp4视频添加至项目中(记得勾选Add to Target)。运行应用,视频应该会逐一播放。

颜色拾取器

SwiftUI自带了​​ColorPicker​​视图来允许用户选取颜色。该视图创建一个按钮,打开预定义界面,自带有选取和配置颜色的工具。以下是该视图的初始化方法。

  • ColorPicker(String, selection: Binding, supportsOpacity: Bool):本初始化方法创建一个颜色拾取器。第一个参数为显示在按钮旁的标签,​​selection​​​参数是一个绑定属性,存储用户所选颜色的​​Color​​​视图,​​supportsOpacity​​​参数指定是否允许用户设置透明度。默认值为​​true​​。

颜色拾取器的实现非常简单。我们用​​Color​​​视图定义一个​​@State​​​属性,然后使用它初始化​​ColorPicker​​视图,这样每次用户选择颜色时,就会存储到该属性中,我们可以使用它来修改其它视图。在下例中,我们使用该属性的值来修改界面的背景色。

示例18-31:显示颜色拾取器

struct ContentView: View {@State private var selectedColor: Color = .whitevar body: some View {VStack {ColorPicker("Select a Color", selection: $selectedColor).padding()Spacer()}.background(selectedColor)}
}

​ColorPicker​​​视图展示一个按键,打开用户选择颜色的界面。用户选取颜色后,颜色会自动赋给​​@State​​属性。这意味着用户可以按意愿多次修改选择,但只有最后一次所选的颜色保存到该属性中。

图18-13:颜色拾取器

图18-13:颜色拾取器

✍️跟我一起做:创建一个多平台项目。使用示例18-31中的代码更新​​ContentView​​视图。运行应用、点击颜色拾取器按钮。选择颜色,会看到界面颜色的变化,如图18-13所示。

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

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

相关文章

Notes数据结合报表工具Tableau

大家好,才是真的好。 我希望你看过前面两篇内容《Domino REST API安装和运行》和《Domino REST API安装和运行》,更希望你能看过《Notes数据直接在Excel中统计!》,有了这些内容作为基础,今天的内容就显得特别简单。 …

Freertos任务管理

一.任务状态理论讲解 正在执行的任务状态是running,其他执行的等待执行的任务状态是ready 1.修改间隔时间 2.任务状态 处于各个状态的任务是怎样被管理起来的:链表 3.代码 TaskHandle_t xHandleTask1; TaskHandle_t xHandleTask3;static int task1f…

6个实用又好用的交互原型工具!

在 UI/UX 设计中,原型设计是至关重要的一步。正如用户体验中的其它环节一样,有无数的交互原型工具可以帮助你完成原型设计。市场上有太多的交互原型工具,如果你不知道选择哪一种,那么我们将为你介绍 6 个实用又好用的交互原型工具…

Multidimensional Scaling(MDS多维缩放)算法及其应用

在这篇博客中,我将与大家分享在流形分析领域的一个非常重要的方法,即多维缩放MDS。整体来说,该方法提供了一种将内蕴距离映射到显性欧氏空间的计算,为非刚性形状分析提供了一种解决方案。当初就是因为读了Bronstein的相关工作【1】…

智能优化算法应用:基于鼠群算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于鼠群算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于鼠群算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鼠群算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

6页手写笔记总结信号与系统常考知识大题知识点

题型一 判断系统特性题型二 求系统卷积题型三 求三大变换正反变换题型四 求全响应题型五 已知微分方程求系统传递函数题型六 已知系统的传递函数求微分方程题型七 画出系统的零极点图,并判断系统的因果性和稳定性 (笔记适合快速复习,可能会有…

2023 年最新 FPV 套件评测

FPV 飞行是近年来非常流行的一种新兴运动。它可以让您在第一人称视角下体验飞行的乐趣。FPV 套件可以分为多种类型,根据您的需求和预算,您可以选择合适的套件。 下面我们将对 2023 年最新的几款 FPV 套件进行评测,帮助您选择合适的产品。 Sp…

InST论文复现

论文地址:https://arxiv.org/abs/2211.13203 论文git:https://github.com/zyxElsa/InST 遇到的问题: 1.requests.exceptions.SSLError: HTTPSConnectionPool(hosthuggingface.co, port443): Max retries exceeded with url: /openai/clip-…

一个容器中填值,值太多不换行,而是调小字体大小和行高

<!-- clampLineHeight 重计算行高 --> <!-- clampTextSize 重计算字体大小 --> <!-- 这里的div高8mm, 宽6cm, 文本为text --> <div style"height:8mm;width:6cm;text-align:left"><span :style"{ fontSize: clampTextSize(text, 6cm…

用python测试网络上可达的网络设备

用python测试网络上可达的网络设备 之前使用的os在python中执行ping测试网络中可达的目标&#xff0c;但是他在执行ping命令时脚本会将系统执行ping时的回显内容显示出来&#xff0c;有时这些回显并不是必要的。如果用脚本一次性ping成百上千台网络设备或者URL时会影响美观和阅…

MySQL中的索引①——索引介绍、结构、分类、语法、SQL性能分析

目录 目录 索引概述--> 介绍---> 优缺点---> 索引结构--> ​编辑 存储引擎支持情况---> BTree---> BTree---> Hash---> Hash特点---> 思考题 索引分类--> InnoDB存储引擎中---> 聚集索引---> 二级索引---> 执行过程--…

SpringBootAdmin

SpringBootAdmin 文章目录 SpringBootAdmin创建SpringBootAdmin服务端创建SpringBootAdmin客户端启动应用 总结 github地址 https://github.com/codecentric/spring-boot-admin 可以查到所有的版本号 创建SpringBootAdmin服务端 创建springBoot项目的时候&#xff0c;在ops选项…

外贸平台获客技巧分享!(个人经历)

作为一名从事外贸行业多年的专业人士&#xff0c;我深知在竞争激烈的市场中获取客户的重要性&#xff0c;今天&#xff0c;我想与大家分享一些我在外贸平台上获客的技巧和经验&#xff0c;希望能够帮助到需要的人。 首先&#xff0c;我们需要明确一点&#xff0c;外贸平台是一…

一键抠图|3个智能AI抠图软件实现抠图自由!

听说你对如何利用AI抠图技术去除白色背景感兴趣&#xff1f;设想一下&#xff0c;你有一张某人站在白色背景前的照片&#xff0c;而你只希望能留下这个人物。在过去&#xff0c;你可能需要花费大量时间和精力手动进行抠图。但现在&#xff0c;AI技术来拯救你了&#xff01;AI可…

2024年MCM/ICM美国大学生数学建模竞赛备战指南

01 2024美赛基本要求 1.关于时间&#xff08;北京时间&#xff09; 比赛开始时间&#xff1a; 2024年2月2日6:00至 2024年2月6日9:00 提交截止时间&#xff1a;2024年2月6日10:00 结果发布时间&#xff1a;结果将于2024年5月31日或之前发布 2.关于规则 完整的解决方案现…

Redis应用-缓存

目录 什么是缓存 使用redis作为缓存 缓存的更新策略 通用的淘汰策略 redis内置的淘汰策略 缓存预热 缓存穿透 缓存雪崩 缓存击穿 什么是缓存 缓存(cache)是计算机中一个经典的概念,在很多的场景中都会涉及到. 核心思路就是把一些常用的数据放到触手可及(访问速度更快…

用OpenCV与MFC写一个图像格式转换及简单处理程序

打开不同格式的图形文件&#xff0c;彩色装灰度图像、锐化、高斯滤波、边界检测及将其存储为需求格式是图像处理的最基本的操作。如果单纯用MFC编程&#xff0c;是一个令人头痛的事情&#xff0c;有不少的代码量。可用OpenCV与MFC编程就变得相对简单。下面来详细演示这一编程操…

敏捷:应对软件定义汽车时代的开发模式变革

随着软件定义汽车典型应用场景的落地&#xff0c;汽车从交通工具转向智能移动终端的趋势愈发明显。几十年前&#xff0c;一台好车的定义主要取决于高性能的底盘操稳与动力系统&#xff1b;几年前&#xff0c;一台好车的定义主要取决于智能化系统与智能交互能否满足终端用户的用…

五肽-13|提亮肤色,美白肌肤

五肽-13 INCI名称&#xff1a;五肽-13 说明&#xff1a; 五肽-13是一种合成肽&#xff0c;由丙氨酸、精氨酸、赖氨酸、脯氨酸和缬氨酸组成 功能&#xff1a; 五肽-13起到增白剂的作用 应用程序&#xff1a; 提亮和美白

Unity渲染Stats分析

文章目录 前言一、Stats二、我们主要看渲染状态分析1、FPS2、其他状态信息3、DrawCall4、Batch5、Setpass Call6、在Unity中弱化了DrawCall的概念&#xff0c;我们主要看 Batch 和 Setpass Call 三、使用 Batching&#xff08;合批&#xff09; 降低 Batch &#xff08;渲染批次…