​一文学会iOS画中画浮窗

news2024/11/22 11:46:55

 0e869f27b5d547c0d17e563cadc6c10e.gif

本文字数:11934

预计阅读时间:40分钟

背景

之前有看到有人用画中画实现时分秒的计时,顺手收藏了,一直没来及看。最近使用《每日英语听力》,突然发现它用画中画实现了听力语句的显示,顿时来了兴趣,所以来研究一下是怎么实现的?顺便也研究下画中画时分秒计时的实现——每次遇到某些平台每天固定时间开抢的时候,我都希望iPhone能够显示具体到秒的计时,这样就能知道什么时候开始点击合适,而不是每次都提前一分钟在那里不停的点点点却什么都抢不到...

实现

画中画一般是用来浮窗播放视频的,那如何让画中画播放自定义的界面而不是视频?下面分为5步具体来看下:

  1. 实现画中画功能,需要设置哪些开关,实现哪些方法;

  2. 基本的使用系统播放器时,画中画的实现;

  3. 自定义播放器时,画中画功能的实现又需要如何设置,有哪些不同;

  4. 如何通过画中画实现时分秒计时功能;

  5. 《每日英语听力》通过画中画播放英语听力语句时怎么实现的?

APP支持画中画功能

如何让APP支持画中画功能?首先需要设置App支持BackgroundModes,然后勾选BackgroundModes中的Audio, Airplay, and Picture in Picture

操作如下:

0a30f8e896c4ee7b6c504b2a2bf7dd19.jpeg2cd50b3a8a25da49b5105ef19bdd3803.jpeg

然后需要设置AVAudioSession,在AppDelegate.Swiftapplication(_:didFinishLaunchingWithOptions:)方法设置如下代码:

  1. 导入AVFoundation

  2. 设置AVAudioSession支持后台播放

// 导入AVFoundation
import AVFoundation

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // 添加设置代码
        do {
            // 设置AVAudioSession.Category.playback后,在静音模式下,或者APP进入后台,或者锁定屏幕后还可以继续播放。
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.moviePlayback)
        } catch {
            print(error)
        }

        return true
    }

使用系统播放器时画中画的实现

使用系统播放器AVPlayerViewController来实现播放器画中画,首先导入AVKit,获取要播放的资源,然后使用AVPlayerViewController来进行播放,代码如下:

import AVKit

    /// 获取播放的资源
    fileprivate func playerResource() -> AVQueuePlayer? {
        guard let videoURL = Bundle.main.url(forResource: "suancaidegang", withExtension: "mp4") else {
          return nil
        }

        let item = AVPlayerItem(url: videoURL)
        let player = AVQueuePlayer(playerItem: item)
        player.actionAtItemEnd = .pause
        return player
    }

    @IBAction func systemPlayerAction(_ sender: Any) {
        guard let player = playerResource() else {
            return
        }
        let avPlayerVC = AVPlayerViewController()
        avPlayerVC.player = player
        present(avPlayerVC, animated: true) {
            player.play()
        }
    }

这里需要注意的是,一定要在真机上才可以看到画中画的效果,使用模拟器不行。运行后可以看到AVPlayerViewController直接支持了画中画的播放;点击进入画中画后,之前全屏的播放界面自动关掉;点击画中画返回播放界面后,画中画关闭,但是之前的播放界面也没有重新打开,效果如下:fb1736d717af33c6572486a41a9c7974.gif而这里很明显,画中画返回不了之前的播放界面是有问题的,所以要修改一下,加入可以设置再进入画中画时全屏的播放界面不关闭,点击画中画的返回是否可以正常呢?这里AVPlayerViewControllerDelegate的方法 playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_ playerViewController: AVPlayerViewController) -> Bool可以控制进入画中画时是否关闭当前界面。

// 在此方法中添加avPlayerVC.delegate = self
    @IBAction func systemPlayerAction(_ sender: Any) {
        guard let player = playerResource() else {
            return
        }
        let avPlayerVC = AVPlayerViewController()
        avPlayerVC.delegate = self
        avPlayerVC.player = player
        present(avPlayerVC, animated: true) {
            player.play()
        }
    }

    // 设置AVPlayerViewControllerDelegate
   extension ViewController: AVPlayerViewControllerDelegate {
       func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_ playerViewController: AVPlayerViewController) -> Bool {
            // 返回false时,进入画中画后播放界面不关闭
            // 返回true时,进入画中画后播放界面自动关闭,默认为true
           return false
       }
   }

运行调试效果,可以看到,进入画中画时,播放界面没关闭,显示This video is playing in picture in picture,且没有关闭按钮;画中画返回时,播放界面可以继续接着播放。效果如下:

709d59b210f971121972ff6b11d51eae.gif

对比可以发现,没处理前点击进入画中画,原播放界面消失且关闭,点击画中画中的进入按钮不能进入到播放界面。处理之后,进入画中画,原播放界面消失但不关闭,点击画中画的进入按钮可以返回到全屏播放界面,但是在进入画中画后,原播放界面无法关闭。

eef2492d522f8c7a22bcb948c9ba9066.png

但是上面的效果也不是所期望的,通常进入画中画模式,是为了继续操作页面其他的内容,而上面的设置虽然可以让画中画返回时继续播放,但是却也阻碍了操作页面其他的内容,所以还是需要修改。期望的效果是,进入画中画界面,当前播放界面消失;并且从画中画返回时,还可以进入播放界面,下面来看下如何设置实现:

AVPlayerViewControllerDelegate中有另外一个方法playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) ,画中画点击返回时会触发这个方法,所以要做的内容是,在这个方法被触发时,重新唤起播放视频界面,代码如下:

extension ViewController: AVPlayerViewControllerDelegate {
    func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_ playerViewController: AVPlayerViewController) -> Bool {
        // 这里修改为返回true,即进入画中画时关闭播放界面
        return true
    }

    func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
        restore(playerVC: playerViewController, completionHandler: completionHandler)
    }
}

    fileprivate func restore(playerVC: UIViewController, completionHandler: @escaping (Bool) -> Void) {
        if let presentedVC = presentedViewController {
            // 说明当前正在播放的界面还存在
            // 先关闭界面,再弹出播放界面
            presentedVC.dismiss(animated: false) { [weak self] in
                self?.present(playerVC, animated: false) {
                    completionHandler(true)
                }
            }
        } else {
            // 直接弹出播放界面
            present(playerVC, animated: false) {
                completionHandler(true)
            }
        }
    }

运行查看效果,可以看到进入画中画界面,当前播放界面消失;并且从画中画返回时,还可以进入播放界面,完美。演示如下:

95f18cd73a98ad8e342443034f5fdef9.gif

对比如下,没处理前点击进入画中画后原播放界面消失但不关闭,处理后进入画中画后,原播放界面消失且关闭,且点击画中画中的进入按钮,也可以返回到全屏播放界面。

自定义播放器时画中画的实现

自定义播放器相比于使用系统的AVPlayerViewController,需要在自定义播放器界面实现点击唤起画中画播放,并且实现画中画的代理方法AVPictureInPictureControllerDelegate,在画中画的代理方法中,处理画中画返回时的逻辑。需要着重注意的是,如果设置进入画中画后播放界面消失,则当前的播放界面会被释放掉,会导致播放界面上的画中画播放也会消失,所以需要特殊处理下,声明一个全局的来存储。参考Picture in Picture Across All Platforms

代码如下:

protocol CustomPlayerVCDelegate: AnyObject {
    func playerViewController(
      _ playerViewController: MWCustomPlayerVC,
      restoreUserInterfaceForPictureInPictureStopWithCompletionHandler
        completionHandler: @escaping (Bool) -> Void
    )
}

private var activeCustomPlayerVCs = Set<MWCustomPlayerVC>()
class MWCustomPlayerVC: UIViewController {

    // MARK: - properties
    private var pictureInPictureVC: AVPictureInPictureController?
    weak var delegate: CustomPlayerVCDelegate?
    var autoDismissAtPip: Bool = false // 进入画中画时,是否自动关闭当前播放页面
    var enterPipBtn: CustomPlayerCircularButtonView?

   override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        view.backgroundColor = .black
        
        setupPictureInPictureVC()
        setupEnterPipBtn()
    }

   fileprivate func setupPictureInPictureVC() {
        guard let playerLayer = playerLayer else {
            return
        }

        pictureInPictureVC = AVPictureInPictureController(playerLayer: playerLayer)
        pictureInPictureVC?.delegate = self
    }
       
    fileprivate func setupEnterPipBtn() {
        enterPipBtn = CustomPlayerCircularButtonView(symbolName: "pip.enter", height: 50.0)
        enterPipBtn?.addTarget(self, action: #selector(handleEnterPipAction), for: [.primaryActionTriggered, .touchUpInside])
        view.addSubview(enterPipBtn!)
        
        enterPipBtn?.snp.makeConstraints { make in
            make.right.equalToSuperview().inset(10.0)
            make.centerY.equalTo(self.view.snp.centerY)
            make.width.height.equalTo(50.0)
        }
    }

    // 点击唤起画中画界面
    @objc
    fileprivate func handleEnterPipAction() {
        pictureInPictureVC?.startPictureInPicture()
    }
}

extension MWCustomPlayerVC: AVPictureInPictureControllerDelegate {
    func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
        // 进入画中画播放的代理方法
        activeCustomPlayerVCs.insert(self)
        enterPipBtn?.isHidden = true
    }
    
    // 画中画开始播放后,当前播放界面是否消失
    func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
        if autoDismissAtPip {
            dismiss(animated: true)
        }
    }
    
    // 画中画进入失败
    func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
        activeCustomPlayerVCs.remove(self)
        enterPipBtn?.isHidden = false
    }
    
    // 画中画返回的代理方法
    func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
        delegate?.playerViewController(self, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: completionHandler)
    }
}

然后在外面使用的地方调用,并且处理画中画关闭的回调代理方法,如下:

@IBAction func customPlayerAction(_ sender: Any) {
        guard let player = playerResource() else {
            return
        }
        
        let playerVC = MWCustomPlayerVC()
        playerVC.modalPresentationStyle = .fullScreen
        playerVC.delegate = self
        playerVC.player = player
        playerVC.autoDismissAtPip = true
        present(playerVC, animated: true) {
            player.play()
        }
    }

 extension ViewController: CustomPlayerVCDelegate {
    func playerViewController(
      _ playerViewController: MWCustomPlayerVC,
      restoreUserInterfaceForPictureInPictureStopWithCompletionHandler
      completionHandler: @escaping (Bool) -> Void) {
          restore(playerVC: playerViewController, completionHandler: completionHandler)
      }
}

运行后效果如下,可以看到,自定义的播放器处理后可以和系统自带播放器的画中画效果一样:

bedc5d15586962625dc46f86246cf23f.gif

在开始下一步之前,希望大家能思考一下:通过上面的画中画例子,已经知道画中画是怎么使用的了。那假如让你来实现一个画中画的计时,你会怎么实现,有哪些方法?

  • 笔者想的方法是,既然画中画是播放视频的,那是否可以把view转为视频?然后再用播放视频的方式,来播放view的内容?

  • 然后笔者查阅了网上其他资料,发现还有一种更tricky的思路,既然画中画在APP中弹出,那是不是能获取画中画的window,获取到window之后,直接在window上添加view的显示是不是就可以了?

下面就依次来验证一下这两种方法是否都可行?首先画中画的计时,就来验证方法一是否可行;《每日英语听力》语句的展示,来验证方法二是否可行。

时分秒计时画中画的实现

这里使用方法一,即把view转为视频,再用播放视频的方式来播放view的内容,来实现一个计时器。那么问题是如何把view转为视频?

查找不到直接的转换方法,但是参考UIPiPView,可以发现里面是:

  1. view转为CMSampleBuffer

    (参考https://soranoba.net/programming/uiview-to-cmsamplebuffer);

  2. 通过initWithSampleBufferDisplayLayer方法用AVSampleBufferDisplayLayer来初始化AVPictureInPictureController.ContentSource

  3. 再用AVPictureInPictureController.ContentSource来初始化AVPictureInPictureController

  4. 然后用AVSampleBufferDisplayLayer来展示CMSampleBuffer

  5. 最终把view显示在了AVPictureInPictureController上。

这里需要注意的一个问题是,上面的把view转为CMSampleBuffer,再把CMSampleBuffer显示到AVPictureInPictureController上的过程只是单个view,而如何变成一个流畅的视频播放呢?需要定义不断的刷新timer,那刷新的timer的间隔多少合适呢?肉眼看不到卡顿就合适,UIPiPView中推荐使用0.1/60秒。

这里就不再重复封装,直接使用UIPiPView,然后创建一个计时器,需要注意的是要显示的view是添加在UIPipView上。

代码如下:

import UIKit
import UIPiPView
import SnapKit

class MWFullTimerVC: MWBaseVC {
    
    // MARK: - properties
    private let pipView = UIPiPView()
    private let timeLabel = UILabel()
    private let dateFormatStr = "yyyy-MM-dd HH:mm:ss"
    private var timer: Timer?

    // MARK: - view life cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        view.backgroundColor = UIColor.black
        
        setupPipView()
        setupTimeLabel()
        createDisplayLink()
    }
    
    fileprivate func setupPipView() {
        let width = UIScreen.main.bounds.width
        pipView.frame = CGRect(x: 10.0, y: 0, width: width - 20.0, height: 50.0)
        view.addSubview(pipView)
        pipView.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview().inset(10.0)
            make.top.equalToSuperview().inset(100.0)
            make.height.equalTo(50.0)
        }
    }
    
    fileprivate func setupTimeLabel() {
        timeLabel.font = UIFont.boldSystemFont(ofSize: 16.0)
        timeLabel.textColor = UIColor.white
        timeLabel.backgroundColor = UIColor.orange
        timeLabel.textAlignment = .center
        pipView.addSubview(timeLabel)
        timeLabel.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            make.top.equalToSuperview()
            make.height.equalToSuperview()
        }
    }
    
    func createDisplayLink() {
        timer = Timer(timeInterval: 0.1/60, repeats: true, block: { [weak self] _ in
            self?.refresh()
        })
        RunLoop.current.add(timer!, forMode: RunLoop.Mode.common)
        timer?.fire()
    }

    
    // MARK: - init
    
    
    // MARK: - utils
    func reloadTime() {
        let date = Date()
        let formatter = DateFormatter()
        formatter.dateFormat = dateFormatStr
        self.timeLabel.text = formatter.string(from: date)
    }

    
    // MARK: - action
    
    func refresh() {
        reloadTime()
    }
    
    override func handleEnterPipAction() {
        super.handleEnterPipAction()
        if pipView.isPictureInPictureActive() {
            pipView.stopPictureInPicture()
        } else {
            pipView.startPictureInPicture(withRefreshInterval: 0.1/60.0)
        }
    }
    
    // MARK: - other
  
}

运行后调试效果如下:

c8ffc64aacddee6954f8b55ba44dd71e.gif

可以看到上面的方法是可行的,而且画中画的大小是可自己定义的,同时不需要内置空白的视频文件。定义的视图什么样画中画的显示就是什么样。

《每日英语听力》画中画的实现

这里来验证获取画中画的window,获取到window后直接在window上添加view的显示,从而在画中画中显示自定义view的方式。

在画中画即将展示的代理方法pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController)中,获取到最上层window,然后添加自定义文字播放view。文字播放view设置每隔2秒播放下一句。

最终整体代码如下:

class MWPipWindowVC: UIViewController {
    func setupTextPlayerView(on targetView: UIView) {
        targetView.addSubview(textPlayView)
        textPlayView.text = text1
        
        textPlayView.snp.makeConstraints { make in
            make.centerY.equalTo(targetView.snp.centerY)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(250.0)
        }
    }

       fileprivate func setupTimer() {
        timer = Timer(timeInterval: 2.0, repeats: true, block: { [weak self] _ in
            self?.handleTimerAction()
        })
        RunLoop.current.add(timer!, forMode: RunLoop.Mode.common)
        timer?.fire()
    }

    // MARK: - utils
    
    // MARK: - action
    func handleTimerAction() {
        let dataList = [text1, text2, text3, text4, text5]
        count += 1
        let index = count % 5
        let str = dataList[index]
        textPlayView.text = str
    }
  }

extension MWPipWindowVC: AVPictureInPictureControllerDelegate {
    func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
        activeCustomPlayerVCs.insert(self)
        enterPipBtn?.isHidden = true
        if let window = UIApplication.shared.windows.first {
            setupTextPlayerView(on: window)
        }
    }
    xxx
}

运行调试,最终效果如下:、

290fd6865b44ee5ac90c5b047f22b854.gif

可以看到上面的方法是可行的,但是需要注意的是,这里进入画中画时可以看到播放视频的界面闪了一下,而且画中画的尺寸是和视频尺寸一致的。所以随用这种方法时,需要提前准备好对应尺寸的空白视频,然后使用画中画播放空白视频,再把自定义的view添加到画中画的window上。

对比"view转为视频然后播放"和"播放空白视频然后将view展示在视频上方的window中"这两种实现,首先两者都可以实现自定义画中画展示的效果。但是笔者测试后发现:"view转视频再播放"占用的CPU要比后者高很多,因为需要不断的读取和刷新。而"播放空白视频然后将view展示在视频上方的window中"这种方法虽然占用的CPU低,但是因为依赖空白视频文件,所以安装包体积会被变大,而且如果要有多种样式的画中画效果,就需要多个空白视频文件。

所以两者要如何选择使用哪种方式呢?通常来说,如果没有多个画中画样式的需求,建议选择"播放空白视频然后将view展示在视频上方的window中";而如果对安装包大小敏感,且需要用户自定义画中画或者有不同样式的画中画需求,则可以考虑使用"view转为视频然后播放"的方法。

总结

2a5a31ee0d7ddb39aa999cde8c462195.png

参考

  • Adopting Picture in Picture in a Standard Player

  • Picture in Picture Across All Platforms

  • UIPiPView

  • CustomPictureInPicture

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

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

相关文章

超线程技术

超线程&#xff08;HT, Hyper-Threading&#xff09;是英特尔研发的一种技术&#xff0c;于2002年发布。超线程技术原先只应用于Xeon 处理器中&#xff0c;当时称为“Super-Threading”。之后陆续应用在Pentium 4 HT中。早期代号为Jackson。 [1] 通过此技术&#xff0c;英特尔实…

Golang每日一练(leetDay0077) 存在重复元素、天际线问题

目录 217. 存在重复元素 Contains Duplicate &#x1f31f; 218. 天际线问题 The Skyline Problem &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 …

PyTorch-torchvision

1. 以Torchvision中的CIFAR10为例 dataset.py Ps&#xff1a;如果是从其他地方下载的gz文件&#xff0c;直接建立一个dataset文件夹然后将gz文件放进去&#xff0c;再运行。 import torchvisiontrain_set torchvision.datasets.CIFAR10(root"./dataset", trainTr…

SpringCloud Alibaba Nacos

SpringCloud Alibaba Nacos Nacos 基础 1 官网 1.1 https://github.com/alibaba/Nacos 2 Nacos 是什么&#xff1f; 2.1 一句话: Nacos 就是注册中心[替代Eureka]配置中心[替代Config] 2.2 Nacos&#xff1a;Dynamic Naming and Configuration Service 2.3 Nacos&#xff…

数据结构---二叉树(C语言)

二叉树 1. 二叉树1.1 二叉树的遍历1.1.1二叉树前中后序遍历&#xff08;递归版&#xff09;1.1.2 层序遍历 1.2 二叉树的其他相关接口1.2.1 求二叉树的结点数量1.2.2 求叶子结点个数1.2.3 求树高1.2.4 求第k层结点个数1.2.5 查找二叉树值为k的结点 1. 二叉树 空树非空&#xff…

跨境DNS解析失败问题解决

新钛云服已累计为您分享748篇技术干货 问题 公司使⽤的是阿⾥云基础设施&#xff0c;DNS解析使⽤的是境外的Akamai作为域名的DNS解析服务商。 当前有部分应⽤需要被第三⽅应⽤调⽤&#xff0c;同时也有主动调⽤第三⽅应⽤的需求。最近突发了很多调⽤失败问题。 应⽤调⽤失败&am…

YB时代,正寻找新的数据支点

每个人可能都想过这样一些问题&#xff1a;我们的地球&#xff0c;能够承载多少人口&#xff1f;地球上的石油和煤炭能用多久&#xff1f;碳排放的极限在哪里&#xff1f; 但你有没有想过&#xff0c;我们的地球&#xff0c;能够承载多少数据&#xff1f; 根据《数据存储2030白…

手把手教你打造自己的 AI 虚拟主播

零、写在前面一、 准备环境二、配置软件环境2.1 OBS 安装2.2. VTube Studio 安装 三、开启 B 站直播四、开启 AI 主播五、直播最终效果 零、写在前面 AI 直播在近年来得到了快速的发展和普及&#xff0c;它已经成为了直播平台的重要组成部分。目前&#xff0c;许多知名的直播平…

「读书感悟系列」生命的礼物 · 关于爱、死亡及存在的意义

作者 | gongyouliu 编辑 | gongyouliu 最近花了不到一周时间看完了著名的团体心理治疗大师欧文D.亚隆和他的妻子玛丽莲亚隆合著的这本『生命的礼物 关于爱、死亡及存在的意义』。这本书非常特别&#xff0c;这是玛丽莲在87岁得癌症后跟丈夫一起合作的&#xff0c;每人写一章&a…

URLConnection(二)

文章目录 1. 缓存2. Java的Web缓存 1. 缓存 Web浏览器会缓存页面和图片&#xff0c;将资源缓存在本地&#xff0c;每次需要时会从缓存中重新加载&#xff0c;而不是每次都请求远程服务器。一些HTTP首部&#xff08;包括Expires和Cache-Control&#xff09;可以控制首部。默认情…

OJ练习第116题——二进制矩阵中的最短路径(BFS)

二进制矩阵中的最短路径 力扣链接&#xff1a;1091. 二进制矩阵中的最短路径 题目描述 给你一个 n x n 的二进制矩阵 grid 中&#xff0c;返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径&#xff0c;返回 -1 。 二进制矩阵中的 畅通路径 是一条从 左上角 单元格&am…

记一次Redis消息订阅序列化和反序列化的错误

1、使用的SpringBoot&#xff1b; 2、Redis的Config配置了JSON序列化&#xff0c;覆盖JDK序列化&#xff0c;便于中文查看&#xff0c;配置文件使用ConditionalOnProperty断言&#xff1b; 3、Nacos动态配置&#xff1b; 解决思路&#xff1a; 1、查看Redis中存入的数据乱码&am…

最佳WP Grid Builder评测:灵活的网格和过滤器

当您坐下来观看足球比赛时&#xff0c;您从一英里外都很容易看到超级巨星。 时尚而精致的比赛让他们与众不同&#xff0c;并且比赛的结果经常改变。球迷和经理们都喜欢他们&#xff0c;因为当他们踢球时&#xff0c;他们处于绝对最佳状态。 这同样适用于音乐界的巨星。通常&a…

Go中的异常处理(基础)

Go 中异常处理 主要掌握 一下几个方面: 掌握error接口掌握defer延迟掌握panic及recover error接口 error是指程序中出现不正常的情况,从而导致程序无法正常运行; go中为错误的类型提供了简单的错误处理机制 go中error的源码: // The error built-in interface type is t…

舵机云台实现追踪球形目标功能

1. 功能说明 在样机舵机云台上安装一个摄像头&#xff0c;本文示例将实现舵机云台追踪球形物体的功能。 2. 电子硬件 在这个示例中&#xff0c;我们采用了以下硬件&#xff0c;请大家参考&#xff1a; 主控板 Basra主控板&#xff08;兼容Arduino Uno&#xff09;‍ 扩展板 Bi…

Linux之创建进程、查看进程、进程的状态以及进程的优先级

文章目录 前言一、初识fork1.演示2.介绍3.将子进程与父进程执行的任务分离4.多进程并行 二、进程的状态1.进程的状态都有哪些&#xff1f;2.查看进程的状态2.运行&#xff08;R&#xff09;3.阻塞4.僵尸进程&#xff08;Z&#xff09;1.僵尸状态概念2.为什么要有僵尸状态&#…

Rust每日一练(Leetday0011) 下一排列、有效括号、搜索旋转数组

目录 31. 下一个排列 Next Permutation &#x1f31f;&#x1f31f; 32. 最长有效括号 Longest Valid Parentheses &#x1f31f;&#x1f31f;&#x1f31f; 33. 搜索旋转排序数组 Search-in-rotated-sorted-array &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷…

开发 Microsoft 365 Copilot Plugin!

大家好&#xff01;我是韩老师。 昨天凌晨的微软 Build 大会&#xff0c;大家都看了吗&#xff1f; 我看到了凌晨两点&#xff0c;且毫无困意&#xff01; 真的是干货满满~ 列举几个比较大的发布&#xff1a;1. Windows Copilot2. Bing 成为 ChatGPT 的默认搜索体验3. AI 插件生…

一起看 I/O | 移动设备、Web、AI 和 Google Cloud 更新一览

作者 / Developer X 副总裁兼总经理、开发者关系主管 Jeanine Banks 感谢您再次与我们共享 Google I/O 大会的精彩&#xff01;我们正在持续深度投入资源来提升 AI、移动设备、Web 和 Google Cloud&#xff0c;致力于让您的开发工作更加轻松。现在&#xff0c;您看到了许多我们…

实测「360智脑」的真正实力:能否领跑国内百“模”大战?

ChatGPT 的发布&#xff0c;无疑掀起了一股“AI 技术”新浪潮。百度文心一言、华为盘古、商汤日日新、阿里通义千问、讯飞星火等众多大模型的接连问世&#xff0c;使得国内的“百模之战”进入了前所未有的白热化阶段。无论是各大互联网巨头&#xff0c;还是清华、复旦等知名高校…