diff --git a/README.md b/README.md
index 4f3857e..423ef97 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ Plugin repository for [Macast](https://github.com/xfangfang/Macast/).
| PotPlayer | | | ☑️ | PotPlayer support for Macast | [potplayer.py](https://raw.githubusercontent.com/xfangfang/Macast-plugins/main/potplayer/potplayer.py) [README](https://github.com/xfangfang/Macast-plugins/tree/main/potplayer) |
| PI-FM-RDS | | ☑️ (Only for RaspberryPi) | | Use this to send MP3 music in the form of FM broadcast | [pi_fm.py](https://raw.githubusercontent.com/xfangfang/Macast-plugins/main/pi-fm-rds/pi_fm.py) [README](https://github.com/xfangfang/Macast-plugins/tree/main/pi-fm-rds) |
| WebBrowser | ☑️ | ☑️ | ☑️ | This can be used to download media files or get some m3u8 played | [web.py](https://raw.githubusercontent.com/xfangfang/Macast-plugins/main/web/web.py) [README](https://github.com/xfangfang/Macast-plugins/tree/main/web) |
+| BiliBrowser | ☑️ | ☑️ | ☑️ | Open Bilibili videos in the default browser (depends on NVA to get BV id) | [bilibrowser.py](https://raw.githubusercontent.com/pasical/Macast-plugins/main/bilibrowser/bilibrowser.py) [README](https://github.com/pasical/Macast-plugins/tree/main/bilibrowser) |
| Nirvana(Protocol) | ☑️ | ☑️ | ☑️ | A protocol created by Bilibili. | [nirvana.py](https://raw.githubusercontent.com/xfangfang/Macast-plugins/main/nirvana/nirvana.py) [README](https://github.com/xfangfang/Macast-plugins/tree/main/nirvana) |
diff --git a/README_ZH.md b/README_ZH.md
index a50ef5d..535bfc7 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -31,6 +31,7 @@
| PotPlayer | | | ☑️ | PotPlayer 适配插件 | [potplayer.py](https://gitee.com/xfangfang/Macast-plugins/raw/main/potplayer/potplayer.py) [README](https://gitee.com/xfangfang/Macast-plugins/tree/main/potplayer) |
| PI-FM-RDS | | ☑️ (只能用在树莓派上) | | 使用这个插件接收mp3媒体链接并用pi-fm-rds以fm广播的形式发送 | [pi_fm.py](https://gitee.com/xfangfang/Macast-plugins/raw/main/pi-fm-rds/pi_fm.py) [README](https://gitee.com/xfangfang/Macast-plugins/tree/main/pi-fm-rds) |
| WebBrowser | ☑️ | ☑️ | ☑️ | 这个插件可以用于使用浏览器下载媒体文件,或播放某些不受mpv支持的m3u8文件 | [web.py](https://gitee.com/xfangfang/Macast-plugins/raw/main/web/web.py) [README](https://gitee.com/xfangfang/Macast-plugins/tree/main/web) |
+| BiliBrowser | ☑️ | ☑️ | ☑️ | 在默认浏览器打开 B 站视频(依赖 NVA 协议获取 BV 号) | [bilibrowser.py](https://raw.githubusercontent.com/pasical/Macast-plugins/main/bilibrowser/bilibrowser.py) [README](https://github.com/pasical/Macast-plugins/tree/main/bilibrowser) |
| Nirvana(协议) | ☑️ | ☑️ | ☑️ | 这个插件用于适配B站的私有协议,仅用于编程学习使用,禁止用做商业用途,如有侵权,请联系作者删除。 | [nirvana.py](https://gitee.com/xfangfang/Macast-plugins/raw/main/nirvana/nirvana.py) [README](https://gitee.com/xfangfang/Macast-plugins/tree/main/nirvana) |
diff --git a/bilibrowser/README.md b/bilibrowser/README.md
new file mode 100644
index 0000000..bfd5836
--- /dev/null
+++ b/bilibrowser/README.md
@@ -0,0 +1,33 @@
+# BiliBrowser
+
+## English
+
+BiliBrowser renderer for Macast. It opens the original Bilibili video in your default browser.
+
+### Platform
+
+darwin win32 linux
+
+### Dependence
+
+Requires [NVA (Nirvana) Protocol](https://github.com/xfangfang/Macast-plugins/blob/main/nirvana/README.md) plugin to resolve aid/cid. **After installing, switch the adapter to NVA in Macast settings.**
+
+### Help
+
+Download the plugin to the renderer folder, enable BiliBrowser in Macast. When you cast a Bilibili stream to Macast, the plugin opens ```https://www.bilibili.com/video/BV...```. Make sure to manually switch the adapter option to NVA; otherwise, your browser will open an m3u8 player.
+
+## 中文
+
+BiliBrowser 是为 Macast 开发的播放器插件,会在默认浏览器中打开哔哩哔哩视频源链接。
+
+### 平台
+
+darwin win32 linux
+
+### 依赖
+
+依赖 [NVA 协议](https://github.com/xfangfang/Macast-plugins/blob/main/nirvana/README.md) 用于解析 aid/cid。**安装后请在 Macast 的适配器设置中手动切换为 NVA。**
+
+### 使用帮助
+
+将插件放在 renderer 文件夹,在 Macast 中启用 "BiliBrowser"。当向 Macast 投送 B 站内容时,插件会在浏览器打开 ```https://www.bilibili.com/video/BV...```。请务必手动将适配器选项切换为 NVA,否则浏览器会直接打开 m3u8 播放器。
\ No newline at end of file
diff --git a/bilibrowser/bilibrowser.py b/bilibrowser/bilibrowser.py
new file mode 100644
index 0000000..aa7a06b
--- /dev/null
+++ b/bilibrowser/bilibrowser.py
@@ -0,0 +1,131 @@
+
+# BiliBrowser
+#
+# Macast Metadata
+# BiliBrowser
+# BiliBrowserRenderer
+# darwin,linux,win32
+# 0.2
+# 0.7
+# Pasical
+# Open the stream source BV video in browser. Dependence on NVA Protocol
+
+import re
+import time
+import threading
+import webbrowser
+import subprocess
+import sys
+import cherrypy
+
+from macast import gui, Setting
+from macast.renderer import Renderer
+
+class BiliBrowserRenderer(Renderer):
+ # AV转BV常量
+ XOR_CODE = 23442827791579
+ MASK_CODE = 2251799813685247
+ MAX_AID = 1 << 51
+ ALPHABET = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf"
+ ENCODE_MAP = (8, 7, 0, 5, 1, 3, 2, 4, 6)
+ BASE = len(ALPHABET)
+ PREFIX = "BV1"
+
+ def __init__(self):
+ super(BiliBrowserRenderer, self).__init__()
+ self.start_position = 0
+ self.position_thread_running = True
+ self.position_thread = threading.Thread(target=self.position_tick, daemon=True)
+ self.position_thread.start()
+
+ def position_tick(self):
+ while self.position_thread_running:
+ time.sleep(1)
+ self.start_position += 1
+ sec = self.start_position
+ position = '%d:%02d:%02d' % (sec // 3600, (sec % 3600) // 60, sec % 60)
+ self.set_state_position(position)
+
+ # 从NVA协议获取aid
+ def get_video_info_from_nva(self, url=None):
+ try:
+ target_cid = None
+ if url:
+ match = re.search(r'upgcxcode/\d+/\d+/(\d+)', url)
+ target_cid = match.group(1) if match else None
+
+ protocols = cherrypy.engine.publish('get_protocol')
+ if protocols and hasattr(protocols[0], 'clients'):
+ for handler in protocols[0].clients.values():
+ if hasattr(handler, 'aid') and handler.aid:
+ if target_cid and hasattr(handler, 'cid') and str(handler.cid) == str(target_cid):
+ return handler.aid
+ elif not target_cid:
+ return handler.aid
+ except:
+ pass
+ return None
+
+ # AV号转BV号
+ def av2bv(self, aid):
+ try:
+ aid = int(aid)
+ bvid = [""] * 9
+ tmp = (self.MAX_AID | aid) ^ self.XOR_CODE
+ for i in range(len(self.ENCODE_MAP)):
+ bvid[self.ENCODE_MAP[i]] = self.ALPHABET[tmp % self.BASE]
+ tmp //= self.BASE
+ return self.PREFIX + "".join(bvid)
+ except:
+ return None
+
+ # 在默认浏览器中打开URL
+ def open_browser(self, url):
+ try:
+ if sys.platform == 'darwin':
+ subprocess.Popen(['open', url])
+ elif sys.platform == 'win32':
+ webbrowser.open(url)
+ else:
+ env = Setting.get_system_env()
+ toDelete = []
+ for (k, v) in env.items():
+ if k != 'PATH' and 'tmp' in v:
+ toDelete.append(k)
+ for k in toDelete:
+ env.pop(k, None)
+ subprocess.Popen(["xdg-open", url], env=env)
+ return True
+ except:
+ return False
+
+ def set_media_stop(self):
+ self.set_state_transport('STOPPED')
+ cherrypy.engine.publish('renderer_av_stop')
+
+ def set_media_url(self, url, start=0):
+ self.set_media_stop()
+ self.start_position = 0
+
+ aid = self.get_video_info_from_nva(url)
+ if aid:
+ bvid = self.av2bv(aid)
+ video_url = f"https://www.bilibili.com/video/{bvid}" if bvid else f"https://www.bilibili.com/video/av{aid}"
+ self.open_browser(video_url)
+ self.set_state_transport("PLAYING")
+ cherrypy.engine.publish('renderer_av_uri', video_url)
+ return
+
+ # 如果NVA协议无法获取信息,直接打开原始链接
+ self.open_browser(url)
+ self.set_state_transport("PLAYING")
+ cherrypy.engine.publish('renderer_av_uri', url)
+
+ def stop(self):
+ super(BiliBrowserRenderer, self).stop()
+ self.set_media_stop()
+ self.position_thread_running = False
+
+
+if __name__ == '__main__':
+ gui(BiliBrowserRenderer())