MoreRSS

site iconMumulhl

00后,分享编程、技巧、生活
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Mumulhl的 RSS 预览

nginx 搭建极简直播服务

2024-09-30 20:31:22

当我们像在仅需要在局域网内进行直播这种情况时,就可以自己动手搭建极简的直播服务。

预备知识

会写一点点 nginx 配置。

概念

RTMP

用于流媒体传输的协议,最初用于 Flash 播放器,也可以用于直播。

本教程中,我们将用该协议将直播流推送至 nginx 服务器。

HLS

基于 HTTP 流媒体传输协议,它将流分割成多个文件传输,对于直播流,以 m3u8 文件为播放列表,以 ts 文件为视频。

本教程中,该协议用于向浏览器传输直播流。

编译 nginx

nginx 本身并不支持 RTMP 协议,需要把 nginx-rtmp-module 模块编译进 nginx。

nginx.org 下载 nginx 源码,解压后进入目录。

然后克隆 nginx-rtmp-module 源码:

1
git clone https://github.com/arut/nginx-rtmp-module --depth=1

编译并安装 nginx:

1
2
3
./configure --add-module=nginx-rtmp-module
make
make install

最后 /usr/local/nginx/sbin 添加到环境变量 PATH 中。

前端

一般浏览器不能直接播放 HLS,这里采用 DPlayer + hls.js 用于播放 (因为好看)

这里 还有 这里 下载 DPlayer.min.jshls.min.js/usr/local/nginx/html/

/usr/local/nginx/html/index.html 改为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>直播</title>
</head>

<body>
 <div id="dplayer"></div>
 <script src="hls.min.js"></script>
 <script src="DPlayer.min.js"></script>
 <script>
 const dp = new DPlayer({
 container: document.getElementById('dplayer'),
 autoplay: true, // 自动播放
 live: true, // 直播
 video: {
 url: '/live/example.m3u8', // 等下 example 改成自己的推流码
 type: 'hls',
 },
 });
 </script>
</body>

</html>

打开首页就是播放器。

配置

/usr/local/nginx/html/conf/nginx.conf 改为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
worker_processes 1;


events {
 worker_connections 1024;
}

rtmp {
 server {
 listen 1935;

 chunk_size 4000;

 application live { # /live 是推流地址
 live on;
 hls on;
 hls_path html/live;
 }
 }
}

http {
 server {
 listen 8080;

 location / {
 root html;
 add_header Cache-Control no-cache;
 }
 }
}

关于 nginx-rtmp-module 更多配置见 wiki

不用担心 HLS 的 ts 文件会无限增加下去,nginx-rtmp-module 会删除老的 ts 文件。

启动 nginx:

1
nginx

推流

这里推流使用 OBS Studio

在“设置”-“直播”中填写“服务器”为 http://localhost:8080/live,“推流码”填自己想要的,但 index.html 中的 example 要改成这个推流码,不然没法播放直播。

配置好来源就可以开始直播啦~

试试看

打开 http://localhost:8080 就可以看到直播了,大概会有 10s 左右的延迟。

正式使用时,请根据自己的需求更改。

结语

过去,学校里的大型表演,能到现场看的人数有限,不是所有人都能到现场看,而且没有直播。再过不久有一个歌唱比赛(我当然去参加海选了,只不过没选上),我想搭建一个直播服务去直播这些表演,于是就有了这篇教程。因为能否直播还要征得校方同意,所以不一定能用得上…

给 Android 换个配色

2024-08-15 15:25:12

介绍

Material You 加入到 Android 12 中,其中包括了动态配色。

用户可以直接在系统层面上很方便地改变系统配色和支持动态配色的软件的配色。开发者也不需要再开发配色功能。

设置

(因为不同厂商把选项名改得不一样,所以很难明确写出选项的名称)

  1. 打开设置
  2. 点击一个名称大致包含了 桌面壁纸 的选项
  3. 点击名称像 系统风格 的选项,就可以选择颜色了。

可以单独选择配色,也可以从壁纸上提取出颜色。

结语

用了 Android 13 一整年了,都不知道有这个功能 😂 最近用 Flutter 开发词典的时候才了解到。

将 Android 作为 Linux 的麦克风

2024-08-11 12:40:00

你的电脑可能没有麦克风,需要的时候,又不想买一个麦克风,这时候就可以把你的 Android 当作麦克风来用。

Audio Source 是一个用 ADB 将 Android 麦克风的输入转发到 PulseAudio 进程的工具。

准备

  • 一只手
  • Android 4.0 及以上
  • 一条 USB 数据线,充电线也没问题
  • PATH 中包含 python3、pactl、adb(Archlinux 如果没有 adb,用 sudo pacman -S android-tools 安装,安装后需重启)

使用

Android 端

可在 Releases 页面下载,或在 IzzyOnDroid F-Droid Repository 下载。

安装后,点开软件,如果没看到 UI 很正常,这个软件就是没有 UI 的… 然后要授权软件麦克风和通知权限,如果授权权限的弹窗闪退,可以在 设置 里面授权软件权限。

Linux 端

下载 audiosource 脚本并授权可执行权限。

1
curl -O https://raw.githubusercontent.com/gdzx/audiosource/master/audiosource && chmod +x audiosource

用 USB 数据线连接手机和电脑,在 开发者选项 里开启 USB 调试模式

用 adb 查看一下连接的 Android 设备,这时手机会弹出授权窗口,点授权就完事了。授权完再运行一下这个命令,看看是否正常。

1
adb devices

运行 audiosource 脚本,你的 Linux 就有麦克风啦 :)

1
./audiosource run

结语

Audio Source 是我意外在 F-Droid 找到的,F-Droid 上的宝藏很多 :)

yt-dlp 教程

2024-08-10 20:59:35

yt-dlp 是一个功能强大的命令行音频、视频下载器。yt-dlp fork 自基于 youtube-dl 的已不维护的 youtube-dlc,具有额外的功能和问题修复。

yt-dlp 不仅支持 YouTube,还支持一千多个网站。除了下载音频、视频,还能下载封面。

安装

可以通过 pip 安装,也可以通过你所用的系统的包管理器安装,还可以到 release 页面下载可执行文件。

1
2
3
pip install yt-dlp
# or
pipx install yt-dlp

使用

视频

直接加链接即可。

1
yt-dlp "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

可以下载一整个播放列表的视频。

1
yt-dlp "https://www.youtube.com/playlist?list=PLp8YAQVH95dwCMvzkxUhFy4KWRAtp_awf"

还可以下载 m3u8。

1
yt-dlp "https://example.com/index.m3u8"

列出视频可以下载的格式,也列出了传输协议、格式、分辨率、帧率、大小等信息。

1
2
3
yt-dlp -F "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
# 与下面的命令等价
yt-dlp --list-formats "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

yt-dlp 默认会下载分辨率、帧率等最好的视频,如果要指定相应的分辨率、帧率,要用 --format-sort/-S 参数。

下载分辨率不优于 720p 的视频,也就是下载 720p 的视频。

1
yt-dlp -S "res:720" "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

指定视频的容器格式。

1
yt-dlp -S "ext:webm" "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

, 连接多个限制条件。

1
yt-dlp -S "ext:webm,res:720" "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

嵌入元数据,默认不嵌入。

1
yt-dlp --embed-metadata "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

嵌入封面,默认不嵌入。

1
yt-dlp --embed-thumbnail "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

嵌入字幕,默认不嵌入。仅支持 mp4mkvwebm 容器的视频

1
yt-dlp --embed-subs "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

音频

分离出视频中的音频。

1
2
3
yt-dlp -x "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
# 与下面的命令等价
yt-dlp --extract-audio "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

指定音频的格式和品质,品质取值 0-10,0 最佳,10 最差,默认为 5。

1
yt-dlp -x --audio-format opus --audio-quality 0 "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

嵌入元数据。

1
yt-dlp -x --embed-metadata "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

封面

列出所有封面。

1
yt-dlp --list-thumbnails "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

下载封面,同时会下载视频。

1
yt-dlp --write-thumbnail "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

下载所有封面,不会下载视频。

1
yt-dlp --write-all-thumbnails "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

结语

写不出什么结语了(liao) :(

Dart 中读取 mdict 词典教程

2024-08-09 17:56:10

Dict_reader 是一个用于读取 mdict 词典的 Dart 语言库,支持 MDX/MDD 格式。

README 提供了几个示例,本文提供更适合生产环境的示例。

安装

1
dart pub add dict_reader

使用

在生产环境中,会用到搜索单词、查看单词的功能,而且必须要高效,这时候轮到 SQLite 数据库出场了。在第一次读取词典时,用 Drift 存储少量数据,用于之后快速地搜索和查看。

安装 Drift

1
dart pub add drift drift_flutter dev:drift_dev dev:build_runner

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// database.dart
import "package:dict_reader/dict_reader.dart";
import "package:drift/drift.dart";
import "package:drift/native.dart";
import "dart:io";

part 'database.g.dart';

@TableIndex(name: 'idx_keyText', columns: {#keyText})
class Dictionary extends Table {
 TextColumn get keyText => text()();
 IntColumn get recordBlockOffset => integer()();
 IntColumn get startOffset => integer()();
 IntColumn get endOffset => integer()();
 IntColumn get compressedSize => integer()();
}

@DriftDatabase(tables: [Dictionary])
class AppDatabase extends _$AppDatabase {
 // After generating code, this class needs to define a `schemaVersion` getter
 // and a constructor telling drift where the database should be stored.
 // These are described in the getting started guide: https://drift.simonbinder.eu/getting-started/#open
 AppDatabase() : super(_openConnection());

 @override
 int get schemaVersion => 1;

 Future<void> insertUsers(List<DictionaryCompanion> dictionary) async {
 await batch((batch) {
 batch.insertAll(this.dictionary, dictionary);
 });
 }

 Future<List<DictionaryData>> searchWord(String word) {
 return (select(dictionary)..where((u) => u.keyText.like('$word%'))).get();
 }

 static QueryExecutor _openConnection() {
 return NativeDatabase(File('dictionary.db'));
 }
}

void main() async {
 final database = AppDatabase();
 final dictReader = DictReader("MDX FILE PATH");

 // 不用获取 keyText 和 offset 存入数据库时,可以传入 false 参数
 await dictReader.init();

 // 将 keyText 和 offset 存入数据库,只需一次
 var queue = <DictionaryCompanion>[];
 await for (final (
 keyText,
 (recordBlockOffset, startOffset, endOffset, compressedSize)
 ) in dictReader.read()) {
 queue.add(DictionaryCompanion(
 keyText: Value(keyText),
 recordBlockOffset: Value(recordBlockOffset),
 startOffset: Value(startOffset),
 endOffset: Value(endOffset),
 compressedSize: Value(compressedSize)));
 }

 await database.insertUsers(queue);

 // 通过数据库搜索单词
 final result = (await database.searchWord("go"))[0];
 // 获取单词数据
 print(await dictReader.readOne(result.recordBlockOffset, result.startOffset,
 result.endOffset, result.compressedSize));

 await database.close();
}

然后生成 database.g.dart 文件:

1
dart run build_runner build

这个示例不适合直接放到生产环境中,稍微改一下就可以了。

结语

Dict_reader 主要是翻译 mdict-analysis。我对 Dart 不是很熟,在翻译过程中经常去问 gpt-4o-mini 以及 SearchGPTool,有帮助也有捣乱,最终还是花了四天时间完成。

Dict_reader 并没有完全翻译,而且挑选了最重要、有意义的部分翻译,也基于我自身考虑(我手头没有 mdict 格式 3.0 版本的词库),例如没有校验、不支持 lzo 压缩、不支持 mdict 格式的 3.0 版本,但不影响一般使用。