嵌入式Qt 开发一个视频播放器
上篇文章:嵌入式 Qt开发一个音乐播放器,使用Qt制作了一个音乐播放器,并在OK3568开发板上进行了运行测试,实际测试效果还不错。
本篇继续来实现一个Qt视频播放器软件,可以实现视频列表的显示与选择播放等,先来看下最终的效果:
本篇的Qt代码从野火开发板的例程中移植修改而来,下面分析下程序的代码结构。
1 视频播放器开发总体结构
整个Qt视频播放器项目的代码结构如下:
- 主代码中是视频播放器相关的代码,包括:
- 视频播放器主界面
- 视频名列表界面:在视频播放时可以查看视频列表并切换视频
- Qt视频播放界面:实现单纯的视频播放
- 操作按钮界面:实现播放、暂停、继续、上一个、下一个、进度调节,音量调节
- 播放预览列表界面:在进入视频播放之前的视频预览列表界面
- Ui代码中使用一些Qt的基本功能,包括:
- 一个Qt界面基类
- 滑条功能类
- 图标按钮显示类
- 列表功能类
- 工具类
- 视频帧解析
- 页面列表类
- Skin中是一些图片资源和字体/皮肤定义
- 最后是编译的中间文件和编译结果存储的目录
下面分类介绍了程序的主要代码实现。
2 通用界面代码
上篇介绍音乐播放器时,介绍过一些自定义控件的代码,本篇的视频播放器,可以复用这些代码:
- qtwidgetbase:窗口基类,该类的一个主要功能是可以向窗口内添加图片形式的按钮
- qtsliderbar:用于实现自定义滑条控件,分为水平滑条与竖直滑条
- qtpixmapbutton:图片按钮,支持未按下和按下后的两种样式的图片显示
- qtlistwidget :基础的列表显示
相比于音乐播放器,视频播放器还需要其它额外的功能,如下:
2.1 qttoolbar
工具条的基类,通过创建一个QPropertyAnimation属性动画对象,主要实现工具条的显示与隐藏的动画效果。
例如,在用户无操作一段时间后,将上方的标题栏和下方的播放工具栏隐藏。
主要代码如下:
QtToolBar::QtToolBar(QWidget *parent) : QtWidgetBase(parent)
{m_animation = new QPropertyAnimation(this, "pos");m_animation->setDuration(200);m_animation->setEasingCurve(QEasingCurve::Linear);connect(m_animation, SIGNAL(finished()), this, SIGNAL(signalFinihed()));
}void QtToolBar::SetAnimation(const QPoint &startPos, const QPoint &endPos)
{m_animation->setStartValue(startPos);m_animation->setEndValue(endPos);m_animation->start();
}
2.2 qtvideowidgetsurface
视频帧解析类,继承自QAbstractVideoSurface类,这是一个抽象基类,通过实现它的派生类可以获取来自QMediaPlayer或QCamera视频的帧。
- supportedPixelFormats 用于向Qt返回帧流的图像类型
- present 用于获取到每一帧
class QtVideoWidgetSurface : public QAbstractVideoSurface
{Q_OBJECT
public:QtVideoWidgetSurface(QWidget *widget, QObject *parent = 0);QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;bool isFormatSupported(const QVideoSurfaceFormat &format) const;bool start(const QVideoSurfaceFormat &format);void stop();bool present(const QVideoFrame &frame);QRect videoRect() const { return targetRect; }void updateVideoRect();void paint(QPainter *painter);private:QWidget *widget;QImage::Format imageFormat;QRect targetRect;QSize imageSize;QRect sourceRect;QVideoFrame currentFrame;
};
2.3 qtpagelistwidget
页面形式的预览排布以及左右页面的滑动切换
2.3.1 QtPageListWidgetItem
该类用于表示列表中的每一个项,可以是字符,并且可以带有图片(图标):
class QtPageListWidgetItem
{
public:QtPageListWidgetItem(int id, const QStringList &args);QtPageListWidgetItem(int id, const QString &name);QtPageListWidgetItem(int id, const QString &name, const QPixmap &icon);QtPageListWidgetItem(int id, const QString &path, const QString &name, const QPixmap &icon);int m_nId;QString m_strText;QPixmap m_pixmapIcon;QString m_strBaseName;QString m_strPath;QRect m_rect;QStringList m_strMultiParameters;
};
2…3.2 QtPageListWidget
class QtPageListWidget : public QtWidgetBase
{Q_OBJECT
public:typedef enum{None,LeftDirection,RightDirection} MoveDirection;Q_PROPERTY(int xPos READ Xpos WRITE setXPos)explicit QtPageListWidget(QWidget *parent = 0);~QtPageListWidget();void AddItem(int id, QtPageListWidgetItem *item);void SetItems(const QMap<int, QtPageListWidgetItem *> &items);void SetBackground(const QPixmap &pixmap);void SetBackground(const QColor &color);void SetPageCount(int count);void SetItemLayut(int rows, int columns);void SetItemLayoutSpace(int row, int col);void SetLoopbackChange(bool bOk);signals:void currentItemClicked(QtPageListWidgetItem *item);void currentPageChanged(int index);protected:QMap<int, QtPageListWidgetItem *> m_listItems;// skinQColor m_backgroundColor;QPixmap m_pixmapWallpaper;int m_nPageCnt;int m_nCurrentPage;int m_nPrevPage;int m_nLayoutRows;int m_nLayoutColumns;qreal m_nItemWidth;qreal m_nItemHeight;int m_nDirection;QTimer *m_timerMove;bool m_bLoopbackChange;QPropertyAnimation *m_animationMove;private:bool m_bPressed;QPoint m_startPos;int m_nStartPos;int m_nMoveEndValue;bool m_bRecovery;private:void setXPos(int nValue);int Xpos() { return m_nStartPos; }void resizeRect();
private slots:protected:int m_nCurrentIndex;int m_nHorSpace;int m_nVSpace;protected:void mousePressEvent(QMouseEvent *e);void mouseReleaseEvent(QMouseEvent *);void mouseMoveEvent(QMouseEvent *e);void paintEvent(QPaintEvent *);void drawListItem(QPainter *painter, int page, int xOffset = 0);virtual void drawItemInfo(QPainter *painter, QtPageListWidgetItem *item);
};
注意,该类和qtlistwidget类的功能比较相似
3 视频播放器代码
首先是整体的主界面部分,进行各项功能的初始化显示,主要包括:
- 播放列表初始化(视频预览列表界面)
- 视频播放界面初始化(先隐藏播放界面)
界面效果如下:
主要代码逻辑:
{QtWidgetTitleBar *m_widgetTitle = new QtWidgetTitleBar(this);m_widgetTitle->SetScalSize(Skin::m_nScreenWidth, 80);m_widgetTitle->SetBackground(Qt::transparent);m_widgetTitle->setFont(QFont(Skin::m_strAppFontNormal));m_widgetTitle->SetTitle(tr("视频播放器"), "#ffffff", 32);QVBoxLayout *verLayoutCentor = new QVBoxLayout(this);verLayoutCentor->setContentsMargins(0, 0, 0, 0);verLayoutCentor->setSpacing(0);verLayoutCentor->addWidget(m_widgetTitle, 1);// 播放列表(视频预览列表界面)m_videosList = new VideoListViewer(this);m_videosList->SetBackground(Qt::transparent);connect(m_videosList, SIGNAL(currentItemClicked(QtPageListWidgetItem *)), this, SLOT(SltItemClicked(QtPageListWidgetItem *)));verLayoutCentor->addWidget(m_videosList, 5);// 视频播放界面(先隐藏播放界面)m_videoWidget = new QtVideoWidget(this);m_videoWidget->hide();
}
初始化各个界面后,还要连接对应的槽函数,如切换视频播放列表中的视频时的处理等。
3.1 视频播放
视频播放部分,主要有两部分功能:
- 视频解码播放:使用Qt自带的媒体播放器组件进行音频播放
- 播放时的按钮操作:实现播放、暂停、继续、上一个、下一个、进度调节,音量调节
3.1.1 QMediaPlayer播放视频
这里使用Qt自带的QMediaPlayer组件进行视频的播放,QMediaPlayer播放视频于播放音频的步骤类似。
本项目的视频播放初始化部分:
QtVideoWidget::QtVideoWidget(QWidget *parent) : QtWidgetBase(parent)
{m_urlMedia = QUrl();m_bToolBarShow = false;m_nDuration = 0;m_nPostion = 0;m_player = new QMediaPlayer(this);surface = new QtVideoWidgetSurface(this);m_player->setVideoOutput(surface);m_playList = new MediaPlayListWidget(this);m_playList->setVisible(false);m_player->setPlaylist(m_playList->palyList());connect(m_player, SIGNAL(durationChanged(qint64)), this, SLOT(SltDurationChanged(qint64)));connect(m_player, SIGNAL(positionChanged(qint64)), this, SLOT(SltPostionChanged(qint64)));m_titleBar = new PlayTitleBarWidget(this);connect(m_playList, SIGNAL(signalMediaChanged(QString)), m_titleBar, SLOT(SetText(QString)));connect(m_titleBar, SIGNAL(signalBack()), this, SLOT(SltBackClicked()));m_playBar = new PlayerBarWidget(this);connect(m_playBar, SIGNAL(signalPlay(bool)), this, SLOT(SltBtnPlayClicked(bool)));connect(m_playBar, SIGNAL(currentPostionChanged(int)), this, SLOT(SltChangePostion(int)));connect(m_playBar, SIGNAL(signalPrev()), m_playList->palyList(), SLOT(previous()));connect(m_playBar, SIGNAL(signalNext()), m_playList->palyList(), SLOT(next()));connect(m_playBar, SIGNAL(signalMuenList()), this, SLOT(SltShowMenuList()));connect(m_playBar, SIGNAL(signalVolume()), this, SLOT(SltChangeVolume()));m_timerShow = new QTimer(this);m_timerShow->setSingleShot(true);m_timerShow->setInterval(5000);connect(m_timerShow, SIGNAL(timeout()), this, SLOT(SltAutoCloseToolBar()));m_volumeSlider = new QtSliderBar(this);m_volumeSlider->SetHorizontal(false);m_volumeSlider->SetValue(100);m_volumeSlider->hide();connect(m_volumeSlider, SIGNAL(currentValueChanged(int)), m_player, SLOT(setVolume(int)));
}
3.1.2 播放操作按钮
视频播放时,需要用到播放、暂停、继续、上一个、下一个、进度调节、音量调节等操作按钮,这里的对应按钮使用图标显示为对应的按钮,通过加载QPixmap来实现:
void PlayerBarWidget::InitWidget()
{m_btns.insert(0, new QtPixmapButton(0, QRect(295, 40, 60, 60), QPixmap(":/images/video/ic_prev.png"), QPixmap(":/images/video/ic_prev_pre.png")));m_btns.insert(1, new QtPixmapButton(1, QRect(375, 40, 60, 60), QPixmap(":/images/video/ic_play.png"), QPixmap(":/images/video/ic_pause.png")));m_btns.insert(2, new QtPixmapButton(2, QRect(465, 40, 60, 60), QPixmap(":/images/video/ic_next.png"), QPixmap(":/images/video/ic_next_pre.png")));m_btns.insert(3, new QtPixmapButton(3, QRect(655, 40, 60, 60), QPixmap(":/images/video/ic_volume.png"), QPixmap(":/images/video/ic_volume_pre.png")));m_btns.insert(4, new QtPixmapButton(4, QRect(728, 40, 60, 60), QPixmap(":/images/video/ic_list.png"), QPixmap(":/images/video/ic_list_pre.png")));m_btns.value(1)->setCheckAble(true);// 进度条m_progressBar = new QtSliderBar(this);m_progressBar->SetHorizontal(true);m_progressBar->SetSliderSize(2, 40);connect(m_progressBar, SIGNAL(currentValueChanged(int)), this, SIGNAL(currentPostionChanged(int)));
}
3.2 播放列表
播放列表功能用来实现视频文件的显示,以及手动指定切换要播放的视频。
3.2.1 读取视频文件
通过读取指定目录下的文件,查找类型为mp4的视频文件,构造视频播放列表。
void MediaPlayListWidget::ScanDirMedias(const QString &path)
{QDir dir(path);if (!dir.exists())return;dir.setFilter(QDir::Dirs | QDir::Files);dir.setSorting(QDir::DirsFirst);QFileInfoList list = dir.entryInfoList();int index = m_mapItems.size();for (int i = 0; i < list.size(); i++){QFileInfo fileInfo = list.at(i);if (fileInfo.fileName() == "." || fileInfo.fileName() == ".."){continue;}if (fileInfo.isDir()){ScanDirMedias(fileInfo.filePath());}else if (fileInfo.suffix() == "mp4"){QString strName = fileInfo.baseName().toLocal8Bit().constData();QString strPath = fileInfo.absoluteFilePath().toLocal8Bit().constData();m_playList->addMedia(QUrl::fromLocalFile(strPath));m_mapItems.insert(index, new QtListWidgetItem(index, strPath, strName, QPixmap(":/images/video/ic_video_preview.png")));index++;}}
}
3.2.2 视频列表界面
视频列表的具体界面实现,主要是在对应的矩形位置,显示各个视频文件的名称:
void VideoListViewer::drawItemInfo(QPainter *painter, QtPageListWidgetItem *item)
{painter->save();QFont font(Skin::m_strAppFontBold);font.setPixelSize(18);painter->setFont(font);QPixmap pixmap = item->m_pixmapIcon;int nXoffset = (item->m_rect.width() - pixmap.width()) / 2;int nYoffset = (item->m_rect.height() - pixmap.height() - painter->fontMetrics().height() - 10) / 2;QRect rectPixmap(nXoffset + item->m_rect.left(), item->m_rect.top() + nYoffset, pixmap.width(), pixmap.height());if (pixmap.isNull()){painter->setPen(QColor("#7effffff"));painter->drawRect(item->m_rect);}else{painter->drawPixmap(rectPixmap.topLeft(), pixmap);}painter->setPen(Qt::white);painter->drawText(item->m_rect.left(), rectPixmap.bottom() + 10, item->m_rect.width(), 30, Qt::AlignCenter, item->m_strBaseName);painter->restore();
}
3.3 视频预览列表
在进入正式的视频播放前,会先有一个视频预览列表界面,在这个界面会列出指定搜索目录下的所有视频,点击任意视频图标即可进入播放界面。
此部分的列表绘制代码逻辑如下:
void QtPageListWidget::drawItemInfo(QPainter *painter, QtPageListWidgetItem *item)
{painter->save();QRect rect = item->m_rect;int nXoffset = (rect.width() > ICON_SIZE) ? (rect.width() - ICON_SIZE) / 2 : 0;QRect rectPixmap(rect.left() + nXoffset + ICON_SIZE / 2, rect.top() + ICON_SIZE / 2, ICON_SIZE, ICON_SIZE);if (item->m_pixmapIcon.isNull()){painter->setPen(Qt::NoPen);painter->setBrush(QColor("#02a7f0"));painter->drawEllipse(rectPixmap.topLeft(), ICON_SIZE / 2, ICON_SIZE / 2);}else{painter->drawPixmap(rect.left() + nXoffset, rect.top(), item->m_pixmapIcon);}if (m_nCurrentIndex == item->m_nId){painter->setPen(Qt::NoPen);painter->setBrush(QColor("#32000000"));painter->drawEllipse(rectPixmap.topLeft(), ICON_SIZE / 2, ICON_SIZE / 2);}QFont font = painter->font();font.setPixelSize(16);painter->setFont(font);painter->setPen("#ffffff");int nTextHeight = painter->fontMetrics().height();QRect rectText(rect.left(), rect.bottom() - nTextHeight, rect.width(), nTextHeight);painter->drawText(rectText, Qt::AlignCenter, item->m_strText);painter->restore();
}
4 编译与测试
4.1 在Windows平台编译
Qt程序编写好后,可以先在Windows电脑上编译查看效果,在运行时,可能会遇到如下问题:
DirectShowPlayerService::doRender: Unresolved error code 0x80040266 (IDispatch error #102)
编译的时候没问题,但运行的时候报错:
这是因为Qt 中的多媒体播放,底层是使用DirectShowPlayerService,所以Windows电脑上需要先安装一个DirectShow解码器,例如LAV Filters。可以到这里(https://github.com/Nevcairiel/LAVFilters/releases)下载并安装 LAVFilters:
下载exe安装包安装即可,安装后,再运行就不会报错了,在Windows系统上运行视频播放器的效果如下:
4.2 交叉编译并在板子上运行
通过交叉编译,来测试视频播放器在OK3568-C板子上的播放效果。
使用编译音乐播放器时编写my3568build.sh的编译脚本编译即可,无需修改:
#! /bin/bashmkdir -p build
cd buildexport PATH=/home/xxpcb/myTest/OK3568/sourcecode/OK3568-linux-source/buildroot/output/OK3568/host/bin:$PATHqmake .. && make
执行该脚本即可编译:
./my3568build.sh
将编译成功的可执行文件VIdeoPlayer复制到OK3568-C板子中,然后在其同目录下创建一个video文件夹,里面放入若干个mp4视频文件,然后就可以测试了,此外,代码中,还指定了/userdata/media这个搜索目录,这里有板子自带的一些视频:
文件复制到板子,我这里仍然使用的ADB无线传输的方式,比较方便,具体ADB操作演示,可参考之前这篇文章: RK3568源码编译与交叉编译环境搭建最后的Qt交叉编译与测试部分
需注意的是,板子里的这个Linux系统,不支持中文的显示,视频名不要有中文。
实测效果见文末视频,整体体验播放流畅,视频切换流程,进度条调整播放进度没有音乐播放器调整的顺畅,另外不知道怎么原因,视频播放的声音外放声音特别小,用耳机插孔听是正常的。
5 总结
本篇介绍了使用Qt开发一个视频播放器,一些功能代码是复用上篇的音乐播放器的代码,使用Qt Creator编写视频播放器的代码,首先在Windows电脑上编译运行测试,然后交叉编译,在OK3568-C开发板上进行实际测试。
该视频播放器实现的功能包括基础的播放功能、暂停与继续,音量调节,视频列表显示,下一个、下一个切换,进度条调节播放进度等。
相关文章:

嵌入式Qt 开发一个视频播放器
上篇文章:嵌入式 Qt开发一个音乐播放器,使用Qt制作了一个音乐播放器,并在OK3568开发板上进行了运行测试,实际测试效果还不错。 本篇继续来实现一个Qt视频播放器软件,可以实现视频列表的显示与选择播放等,先…...

阿里巴巴内网 Spring Cloud Alibaba 强势来袭,开创微服务的新时代
Spring Cloud 发展史 Spring Cloud 从 15 年的 3 月份推出之后,迅速在 Java 微服务生态中,成为开发人员的首选技术栈。 Spring Cloud 在 Spring Boot 的基础上,保留 Java 开发习惯,加入分布式特性,提供了一系列通用工…...

边界检测方法总结
1:经典的边界检测方法有sobel,拉普拉斯,canny等。 sobel: def get_sobel(in_chan, out_chan):filter_x np.array([[1, 0, -1],[2, 0, -2],[1, 0, -1],]).astype(np.float32)filter_y np.array([[1, 2, 1],[0, 0, 0],[-1, -2, -…...

Softing dataFEED OPC Suite Extended新版本支持从XML文件中读取生产数据
Softing dataFEED OPC Suite Extended V5.25的新功能——“文件读取(File Read)”,支持访问XML文件中可用的过程数据。 (文件读取功能支持获取由XML文件提供的过程数据)dataFEED OPC Suite Extended是用于OPC通信和云连…...

央行罚单!金融机构被罚原因揭秘
近日,人民银行公布了2023年首批行政处罚罚单,引发业内广泛关注。 顶象防御云业务安全情报中心统计了人民银行官网,2020年1月至2023年2月10日期间,公布的101份行政处罚。 统计显示,16家金融机构被罚27066.9万元&#…...
js中var、let、const详解
首先 var、let、const 在项目开发中都是用来声明变量的,在ES5中只有两种声明变量的方法:var和function,在ES6中新增了 let、const、class、import 四种声明变量的方法,本文主要讲解 var、let 与 const 的语法,其他的大…...
【数据库】MySQL概念知识语法-基础篇(DCL),真的很详细,一篇文章你就会了
目录通用语法及分类DCL(数据控制语言)管理用户查询用户权限控制函数字符串函数数值函数日期函数流程函数约束外键约束多表查询一对多多对多一对一通用语法及分类 ● DDL: 数据定义语言,用来定义数据库对象(数据库、表、字段&…...

Blender骨骼动画快速教程
有关创建模型的更多详细信息,请参阅在 Blender 中创建模型。 我们将为这个例子做一个非常简单的模型——蠕虫! 从我们的初始立方体开始,进入编辑模式,切换到面选择,然后选择任何面: 推荐:将 NSD…...

【C++算法】dfs深度优先搜索(下) ——【全面深度剖析+经典例题展示】
💃🏼 本人简介:男 👶🏼 年龄:18 🤞 作者:那就叫我亮亮叭 📕 专栏:关于C/C那点破事 文章目录0.0 写在前面1. 中国象棋1.1 题干信息① 背景说明概述② 问题描述…...

HIVE 基础(三)
目录 建表语句 表数据 Hive建表高阶语句 - CTAS and WITH CTAS – as select方式建表 CTE (CTAS with Common Table Expression) LIKE 创建临时表 清空表数据 修改表(Alter针对元数据) 改名 修正表文件格式 修改列名 添加列 替换列 动态分…...

redis-cluster集群搭建
安装redis所需环境 yum install -y gcc-c yum install -y wget 创建文件夹 cd / mkdir redis/redis-cluster/7001 cd redis/redis-cluster mkdir 7002 7003 7004 7005 7006 7007 7008下载redis压缩包并解压安装 wget https://download.redis.io/redis-stable.tar.gz tar -…...
【C语言】可变参数列表va_list
本篇博客让我们来认识一下C语言学习过程中往往被忽略的可变参数列表 所谓可变参数,就是一个不限定参数数量的函数,我们可以往里面传入任意个数的参数,以达成某些目的。 关联:C11可变模板参数 1.函数 #include <stdarg.h>…...

CentOS7.6 MySQL8安装
1 检查是否安装过 MySQL rpm -qa | grep -i mysqlmariadb rpm -qa | grep mariadb2 卸载之前的安装 MySQL rpm -e --nodeps 软件名 //强力删除,对相关依赖的文件也进行强力删除卸载 rpm -e --nodeps mariadb-libs-5.5.60-1.el7_5.x86_643 官网下载 MySQL :: D…...
安装Tomcat的步骤?
首先先完成JDK配置,javac -version 检测 1.把tomcat下载到本地硬盘 2.创建tomcat8.0文件夹,完成解压(免安装)4.打开解压之后的目录,进入bin目录,双击startup.bat,启动tomcat5.可以看到弹出一个黑色的窗口,不要关闭,如果关闭意味着…...

Redis之分布式锁
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 Java API并不能提供分布式锁的能力。为了解决这个问题…...

2022年中国前10电商GMV总结
我是卢松松,点点上面的头像,欢迎关注我哦! 1,阿里8万亿;2,京东3万亿;3,拼多多3万亿;4,小程序私域电商3万亿;5,抖音电商1.4万亿。6,抖音本地生活服务电商600亿。7…...
ES6新增扩展:字符串-数值-数组-函数-对象
ES6新增扩展字符串的扩展判断字符串是否包含在另一个字符中字符串补全字符串重复消除字符串空格replaceAll()替换全部字符串at字符串匹配输出数值的扩展数值分隔符检测数值是否有限检测是否为NaNNumber.parseInt()、Number.parseFloat()isInteger()判断是否为整数Math.sign()判…...
python中import原理
0. 前言 在 python 中引入 Module 是再常见不过了,那么当我们 import 时它做了什么事情呢?它是如何加载 Module 使用的呢? 1. 什么是 module? 一般,Module 是一个后缀为 .py 的文件,其 module 名称一般…...

《Qt6开发及实例》6-4 显示SVG格式图片
目录 一、简介与设计 1.1 简介 1.2 设计 二、SvgWidget 2.1 鼠标滚轮事件 三、svgwindow 四、MainWindow 一、简介与设计 1.1 简介 1、SVG 的英文全称是 Scalable Vector Graphics,即可缩放的矢量图形。它是由万维网联盟(W3C)在 200…...

OpenGL ES 绘制一张图片
GLSL 语法与内建函数 GLSL 的修饰符与数据类型 GLSL 中变量的修饰符 const:修饰不可被外界改变的常量attribute:修饰经常更改的变量,只可以在顶点着色器中使用uniform:修饰不经常更改的变量,可用于顶点着色器和片段…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...