MoreRSS

site iconMumulhl修改

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

Inoreader Feedly Follow Feedbin Local Reader

Mumulhl的 RSS 预览

2024年度总结:AI原来能这么好用

2025-01-01 10:07:26

Featured image of post 2024年度总结:AI原来能这么好用

(封面来自 Unsplash,作者 BoliviaInteligente)

2024年结束了,2025年来了,转眼又是一年了。2024给我最大的震撼就是AI的辅助带来的便捷,过去稍有接触AI,但是没有太深入地使用,导致也用不好。

真正对 AI 的接触

看到 X 上有很多大佬用 Cursor 开发出了一些项目,就有点手痒痒了。过去我使用的编辑器是 Helix,一个全键盘编辑器,感觉效率会高一些。如果要用 Cursor 这样的 AI 编辑器就要脱离全键盘的操作了,但是 AI 能提高我的开发效率的话,没有了全键盘操作也不算亏。于是 Cursor 就取代 Helix 在我的电脑上的地位。

Cursor 让我震撼的是,在编辑一块代码的时候,居然可以按 Tab 键去补全其他部分相关的代码,以前我以为 AI 代码补全就只能像补全变量名、函数名那样补全,这完全打破了我的认知啊!

最早使用 AI 我都倾向于想用一句话让 AI 完成我想要做的有点复杂的事,但在理论上是不可行的,事实上也是不行的。只用一句话去表述一件较为复杂的事,信息量很少,难以去描述细节,AI 自然无法做到和自己所想的相符。和人说件事,说话只说一句,得让对方多疯狂,更别说是 AI 了。在我的使用体验来说,AI 更适合去做小任务,而非是复杂的大任务,如果要让 AI 去做大任务,将它分解为多个小任务效果会更好。

AI 的使用体验总结起来就是一个字——“爽”,每周最多一天的时间里我可以开发得更高效。

开源让我学到了很多

2024年新开发的项目主要实在暑假里开发的,好几个我都觉得不太行,所以只挑出我最经常维护的词悦来说吧。

词悦是一个开源的支持 mdict 格式的词典,是我觉得 Android 上没有简洁、好用的开源的 mdict 词典而开发的。虽然最初发布的时候很简陋,不过现在经过几个月的开发,已经完善了许多了。

最初词悦用 git-cliff 生成版本发布时的更新日志,后来我才慢慢在词悦中把 Github 的 label、milestones 功能好好用上,现在词悦的版本更新日志是由 Github 生成的,这样可以把这个版本贡献者直接显示在更新日志中。

词悦还提交到了 F-Droid 上,我还提交过几个 MR,但是水平不太够,没办法,犯了很多低级错误,都由 linsui 大佬纠正了,感谢 linsui 大佬不知疲倦地修改我的 MR!

在开源中,我学会了许多以前不知道的技巧、技术,开源不仅利他,还能利己。

重启博客

2024,我重启了我的博客,一年的文章就是2023的十几倍了(2023只有一篇文章)。2024我写了15篇博文,其中大部分都是在暑假写的。暑假闲来无事重启了博客,还把博客主题换成了 Stack。我还把博客提交到 V2EX VXNA、博友圈等平台上,我的博客也终于不再是我自娱自乐的地方了

对2025的展望

希望我可以在2025把 AI 使用得更加得心应手吧;希望我能在2025凭借自己的能力去赚到人生第一桶金,哪怕没几块也行哪。

给Flutter Android App支持全局上下文菜单

2024-11-30 23:16:10

最近在开发词悦(一个 mdict 词典)的时候,需要支持全局上下文菜单,查了很多资料都没有找到合适的方法,问了下 cursor,得到了初步方案,经过稍微的改动就有了这篇教程。

本文开发环境在 Linux 下。

初始化项目

1
2
flutter create example
cd example

写代码

Manifest

编辑 android/app/src/main/AndroidManifest.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 省略... -->
 <meta-data
 android:name="io.flutter.embedding.android.NormalTheme"
 android:resource="@style/NormalTheme"
 />
 <intent-filter>
 <action android:name="android.intent.action.MAIN"/>
 <category android:name="android.intent.category.LAUNCHER"/>
 </intent-filter>
</activity>
<!-- 新增的部分 -->
<!-- android:label 是上下文菜单中显示的名称 -->
<activity
 android:name=".ProcessTextActivity"
 android:label="example"
 android:exported="true">
 <intent-filter>
 <action android:name="android.intent.action.PROCESS_TEXT" />
 <data android:mimeType="text/plain" />
 <category android:name="android.intent.category.DEFAULT" />
 </intent-filter>
</activity>

原生 Android

创建 android/app/src/main/kotlin/com/example/example/ProcessTextActivity.kt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.example

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity

class ProcessTextActivity : Activity() {
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)

 val text = intent?.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)?.toString() ?: ""

 val intent = Intent(this, MainActivity::class.java).apply {
 action = Intent.ACTION_PROCESS_TEXT
 putExtra(Intent.EXTRA_PROCESS_TEXT, text)
 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) // 如果不加这个 flag,app 在后台运行时无法把选中的文本传给 Flutter
 }

 startActivity(intent)
 finish()
 }
}

编辑 android/app/src/main/kotlin/com/example/example/MainActivity.kt:

 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
package com.example.example

import android.content.Intent
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
 private val CHANNEL = "com.example.example/process_text"
 private var methodChannel: MethodChannel? = null

 override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
 super.configureFlutterEngine(flutterEngine)

 methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
 }

 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)

 if (intent?.action == Intent.ACTION_PROCESS_TEXT) {
 val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)?.toString() ?: ""
 methodChannel?.invokeMethod("processText", text)
 }
 }
}

Flutter 部分

编辑 lib/main.dart:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
 WidgetsFlutterBinding.ensureInitialized();

 platform.setMethodCallHandler((call) async {
 if (call.method == "processText") {
 final text = call.arguments as String; // call.arguments 里就是选中的文本了
 print(text);
 }
 });

 runApp(const MyApp());
}

const platform = MethodChannel("com.example.example/process_text");

结尾

接下来怎么样就靠你的想象力了 :)

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) :(