mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Compare commits
11 Commits
scroll-to-
...
master
Author | SHA1 | Date | |
---|---|---|---|
1b84c7e7ba | |||
21a4b2b797 | |||
5e96bca296 | |||
c78d797fe7 | |||
57596315e9 | |||
0d7887e9d2 | |||
978fd8977d | |||
4ab5901ba9 | |||
11a862f101 | |||
b938a4e09e | |||
e26e4d2004 |
@ -3,7 +3,9 @@ FROM $BASE_IMAGE
|
||||
|
||||
# latest working version with EGL: 111.0.5563.146, revert when resolved
|
||||
# 112.0.5615.49 fails: https://github.com/VirtualGL/virtualgl/issues/229
|
||||
ARG SRC_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_111.0.5563.146-1_amd64.deb"
|
||||
# google does not provide a direct link to the deb file anymore
|
||||
# ARG SRC_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_111.0.5563.146-1_amd64.deb"
|
||||
ARG SRC_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
|
||||
|
||||
#
|
||||
# install google chrome
|
||||
|
@ -313,7 +313,15 @@
|
||||
}
|
||||
|
||||
get clipboard_read_available() {
|
||||
return 'clipboard' in navigator && typeof navigator.clipboard.readText === 'function'
|
||||
return (
|
||||
'clipboard' in navigator &&
|
||||
typeof navigator.clipboard.readText === 'function' &&
|
||||
// Firefox 122+ incorrectly reports that it can read the clipboard but it can't
|
||||
// instead it hangs when reading clipboard, until user clicks on the page
|
||||
// and the click itself is not handled by the page at all, also the clipboard
|
||||
// reads always fail with "Clipboard read operation is not allowed."
|
||||
navigator.userAgent.indexOf('Firefox') == -1
|
||||
)
|
||||
}
|
||||
|
||||
get clipboard_write_available() {
|
||||
@ -630,7 +638,7 @@
|
||||
}
|
||||
|
||||
async syncClipboard() {
|
||||
if (this.clipboard_read_available) {
|
||||
if (this.clipboard_read_available && window.document.hasFocus()) {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText()
|
||||
if (this.clipboard !== text) {
|
||||
|
@ -9,6 +9,7 @@ import * as ko from './ko-kr'
|
||||
import * as fi from './fi-fi'
|
||||
import * as ru from './ru-ru'
|
||||
import * as cn from './zh-cn'
|
||||
import * as tw from './zh-tw'
|
||||
|
||||
export const messages = {
|
||||
en,
|
||||
@ -22,4 +23,5 @@ export const messages = {
|
||||
fi,
|
||||
ru,
|
||||
cn,
|
||||
tw,
|
||||
}
|
||||
|
127
client/src/locale/zh-tw.ts
Normal file
127
client/src/locale/zh-tw.ts
Normal file
@ -0,0 +1,127 @@
|
||||
export const logout = '登出'
|
||||
export const unsupported = '您的網頁瀏覽器不支援 WebRTC'
|
||||
export const admin_loggedin = '您已經以管理員身份登入'
|
||||
export const you = '您'
|
||||
export const somebody = '某人'
|
||||
export const send_a_message = '傳送訊息'
|
||||
|
||||
export const side = {
|
||||
chat: '聊天',
|
||||
files: '檔案',
|
||||
settings: '設定',
|
||||
}
|
||||
|
||||
export const connect = {
|
||||
login_title: '請登入',
|
||||
invitation_title: '您已被邀請進入此房間',
|
||||
displayname: '輸入您的顯示名稱',
|
||||
password: '密碼',
|
||||
connect: '連線',
|
||||
error: '登入錯誤',
|
||||
empty_displayname: '顯示名稱不能為空。',
|
||||
}
|
||||
|
||||
export const context = {
|
||||
ignore: '忽略',
|
||||
unignore: '取消忽略',
|
||||
mute: '靜音',
|
||||
unmute: '解除靜音',
|
||||
release: '強制釋放控制',
|
||||
take: '強制接管控制',
|
||||
give: '移交控制',
|
||||
kick: '踢出',
|
||||
ban: '封鎖 IP',
|
||||
confirm: {
|
||||
kick_title: '踢出 {name}?',
|
||||
kick_text: '您確定要踢出 {name} 嗎?',
|
||||
ban_title: '封鎖 {name}?',
|
||||
ban_text: '您是否要封鎖 {name}?封鎖後需要重新啟動伺服器才能取消封鎖。',
|
||||
mute_title: '靜音 {name}?',
|
||||
mute_text: '您確定要將 {name} 設為靜音嗎?',
|
||||
unmute_title: '解除靜音 {name}?',
|
||||
unmute_text: '您是否要解除 {name} 的靜音?',
|
||||
button_yes: '是',
|
||||
button_cancel: '取消',
|
||||
},
|
||||
}
|
||||
|
||||
export const controls = {
|
||||
release: '釋放控制',
|
||||
request: '請求控制',
|
||||
lock: '鎖定控制',
|
||||
unlock: '解鎖控制',
|
||||
has: '您擁有控制權',
|
||||
hasnot: '您沒有控制權',
|
||||
}
|
||||
|
||||
export const locks = {
|
||||
control: {
|
||||
lock: '鎖定控制(對使用者)',
|
||||
unlock: '解鎖控制(對使用者)',
|
||||
locked: '已鎖定控制(對使用者)',
|
||||
unlocked: '已解鎖控制(對使用者)',
|
||||
notif_locked: '已鎖定使用者控制',
|
||||
notif_unlocked: '已解鎖使用者控制',
|
||||
},
|
||||
login: {
|
||||
lock: '鎖定房間(對使用者)',
|
||||
unlock: '解鎖房間(對使用者)',
|
||||
locked: '房間已鎖定(對使用者)',
|
||||
unlocked: '房間已解鎖(對使用者)',
|
||||
notif_locked: '已鎖定房間',
|
||||
notif_unlocked: '已解鎖房間',
|
||||
},
|
||||
file_transfer: {
|
||||
lock: '鎖定檔案傳輸(對使用者)',
|
||||
unlock: '解鎖檔案傳輸(對使用者)',
|
||||
locked: '檔案傳輸已鎖定(對使用者)',
|
||||
unlocked: '檔案傳輸已解鎖(對使用者)',
|
||||
notif_locked: '已鎖定檔案傳輸',
|
||||
notif_unlocked: '已解鎖檔案傳輸',
|
||||
},
|
||||
}
|
||||
|
||||
export const setting = {
|
||||
scroll: '滾動靈敏度',
|
||||
scroll_invert: '反向滾動',
|
||||
autoplay: '自動播放影片',
|
||||
ignore_emotes: '忽略表情符號',
|
||||
chat_sound: '播放聊天音效',
|
||||
keyboard_layout: '鍵盤配置',
|
||||
broadcast_title: '直播',
|
||||
}
|
||||
|
||||
export const connection = {
|
||||
logged_out: '您已登出。',
|
||||
reconnecting: '正在重新連線…',
|
||||
connected: '已連線',
|
||||
disconnected: '已斷線',
|
||||
kicked: '您已被移出此房間。',
|
||||
button_confirm: '確定',
|
||||
}
|
||||
|
||||
export const notifications = {
|
||||
connected: '{name} 已連線',
|
||||
disconnected: '{name} 已斷線',
|
||||
controls_taken: '{name} 接管了控制權',
|
||||
controls_taken_force: '強制接管控制權',
|
||||
controls_taken_steal: '從 {name} 奪取了控制權',
|
||||
controls_released: '{name} 釋放了控制權',
|
||||
controls_released_force: '強制釋放控制權',
|
||||
controls_released_steal: '從 {name} 強制釋放控制權',
|
||||
controls_given: '將控制權交給 {name}',
|
||||
controls_has: '{name} 擁有控制權',
|
||||
controls_has_alt: '但我已通知對方您有意接管',
|
||||
controls_requesting: '{name} 正在請求控制權',
|
||||
resolution: '將解析度改為 {width}x{height}@{rate}',
|
||||
banned: '已封鎖 {name}',
|
||||
kicked: '已踢出 {name}',
|
||||
muted: '已將 {name} 靜音',
|
||||
unmuted: '已解除 {name} 的靜音',
|
||||
}
|
||||
|
||||
export const files = {
|
||||
downloads: '下載',
|
||||
uploads: '上傳',
|
||||
upload_here: '點選或拖曳檔案至此上傳',
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
- Added nvidia support for firefox.
|
||||
- Added `?lang=<lang>` parameter to the URL, which will set the language of the interface (by @mbattista).
|
||||
- Added `?show_side=1` and `?mute_chat=1` parameter to the URL, for chat mute and show side (by @mbattista).
|
||||
- Added `NEKO_BROADCAST_AUTOSTART` to automatically start or do not start broadcasting when the room is created. By default, it is set to `true` because it was the previous behavior.
|
||||
|
||||
### Bugs
|
||||
- Fix incorrect version sorting for chromium, microsoft-edge, opera and ungoogledchromium.
|
||||
|
@ -107,9 +107,11 @@ nat1to1: <ip>
|
||||
#### `NEKO_BROADCAST_PIPELINE`:
|
||||
- Makes it possible to create custom gstreamer pipeline used for broadcasting, strings `{url}`, `{device}` and `{display}` will be replaced.
|
||||
#### `NEKO_BROADCAST_URL`:
|
||||
- Set a default URL for broadcast streams. Setting this value will automatically enable broadcasting when n.eko starts. It can be disabled/changed later by admins in the GUI.
|
||||
- Set a default URL for broadcast streams. It can be disabled/changed later by admins in the GUI.
|
||||
- e.g. `rtmp://<your-server>:1935/ingest/<stream-key>`
|
||||
|
||||
#### `NEKO_BROADCAST_AUTOSTART`:
|
||||
- Automatically start broadcasting when neko starts and broadcast_url is set.
|
||||
- e.g. `true`
|
||||
### Server
|
||||
|
||||
#### `NEKO_BIND`:
|
||||
|
@ -4,15 +4,18 @@ Neko UI loads, but you don't see the screen, and it gives you `connection timeou
|
||||
|
||||
## Test your client
|
||||
|
||||
Some browser may block WebRTC access by default. You can check if it is enabled by going to `about:webrtc` or `chrome://webrtc-internals` in your browser.
|
||||
Some browsers may block WebRTC access by default. You can check if it is enabled by going to `about:webrtc` or `chrome://webrtc-internals` in your browser.
|
||||
|
||||
Check if your extensions are not blocking WebRTC access. For example, Privacy Badger or Private Internet Access blocks WebRTC by default.
|
||||
Check if your extensions are not blocking WebRTC access. Following extensions are known to block or does not work properly with WebRTC:
|
||||
- Privacy Badger
|
||||
- Private Internet Access
|
||||
- PIA VPN (even if disabled)
|
||||
|
||||
Test whether your client [supports](https://www.webrtc-experiment.com/DetectRTC/) and can [connect to WebRTC](https://www.webcasts.com/webrtc/).
|
||||
|
||||
## Networking
|
||||
|
||||
Most problems are networking related.
|
||||
If you are absolutely sure, that your client is working correctly, then most likely your networking is not set up correctly.
|
||||
|
||||
### Check if your ports are correctly exposed using docker
|
||||
|
||||
@ -59,6 +62,13 @@ Then try to type on one end, you should see characters on the other side.
|
||||
|
||||
If it does not work for you, then most likely your port forwarding is not working correctly. Or your ISP is blocking traffic.
|
||||
|
||||
|
||||
If you get [`Command 'nc' not found.`](https://command-not-found.com/nc) error, you can install `netcat` package using:
|
||||
|
||||
```shell
|
||||
sudo apt-get install netcat
|
||||
```
|
||||
|
||||
### Check if your external IP was determined correctly
|
||||
|
||||
One of the first logs, when the server starts, writes down your external IP that will be sent to your clients to connect to.
|
||||
@ -67,6 +77,8 @@ One of the first logs, when the server starts, writes down your external IP that
|
||||
docker-compose logs neko | grep nat_ips
|
||||
```
|
||||
|
||||
Note: Some newer versions of docker-compose use `docker compose` instead of `docker-compose`.
|
||||
|
||||
You should see this:
|
||||
|
||||
```
|
||||
|
@ -22,7 +22,7 @@ type BroacastManagerCtx struct {
|
||||
started bool
|
||||
}
|
||||
|
||||
func broadcastNew(pipelineFn func(url string) (string, error), defaultUrl string) *BroacastManagerCtx {
|
||||
func broadcastNew(pipelineFn func(url string) (string, error), url string, started bool) *BroacastManagerCtx {
|
||||
logger := log.With().
|
||||
Str("module", "capture").
|
||||
Str("submodule", "broadcast").
|
||||
@ -31,8 +31,8 @@ func broadcastNew(pipelineFn func(url string) (string, error), defaultUrl string
|
||||
return &BroacastManagerCtx{
|
||||
logger: logger,
|
||||
pipelineFn: pipelineFn,
|
||||
url: defaultUrl,
|
||||
started: defaultUrl != "",
|
||||
url: url,
|
||||
started: started && url != "",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
||||
// sinks
|
||||
broadcast: broadcastNew(func(url string) (string, error) {
|
||||
return NewBroadcastPipeline(config.AudioDevice, config.Display, config.BroadcastPipeline, url)
|
||||
}, config.BroadcastUrl),
|
||||
}, config.BroadcastUrl, config.BroadcastAutostart),
|
||||
audio: streamSinkNew(config.AudioCodec, func() (string, error) {
|
||||
return NewAudioPipeline(config.AudioCodec, config.AudioDevice, config.AudioPipeline, config.AudioBitrate)
|
||||
}, "audio"),
|
||||
|
@ -36,6 +36,7 @@ type Capture struct {
|
||||
// broadcast
|
||||
BroadcastPipeline string
|
||||
BroadcastUrl string
|
||||
BroadcastAutostart bool
|
||||
}
|
||||
|
||||
func (Capture) Init(cmd *cobra.Command) error {
|
||||
@ -155,11 +156,16 @@ func (Capture) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("broadcast_url", "", "URL for broadcasting, setting this value will automatically enable broadcasting")
|
||||
cmd.PersistentFlags().String("broadcast_url", "", "a default default URL for broadcast streams, can be disabled/changed later by admins in the GUI")
|
||||
if err := viper.BindPFlag("broadcast_url", cmd.PersistentFlags().Lookup("broadcast_url")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().Bool("broadcast_autostart", true, "automatically start broadcasting when neko starts and broadcast_url is set")
|
||||
if err := viper.BindPFlag("broadcast_autostart", cmd.PersistentFlags().Lookup("broadcast_autostart")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -247,4 +253,5 @@ func (s *Capture) Set() {
|
||||
|
||||
s.BroadcastPipeline = viper.GetString("broadcast_pipeline")
|
||||
s.BroadcastUrl = viper.GetString("broadcast_url")
|
||||
s.BroadcastAutostart = viper.GetBool("broadcast_autostart")
|
||||
}
|
||||
|
@ -46,9 +46,9 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
BORADCAST_STATUS = "broadcast/status"
|
||||
BORADCAST_CREATE = "broadcast/create"
|
||||
BORADCAST_DESTROY = "broadcast/destroy"
|
||||
BROADCAST_STATUS = "broadcast/status"
|
||||
BROADCAST_CREATE = "broadcast/create"
|
||||
BROADCAST_DESTROY = "broadcast/destroy"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -175,7 +175,7 @@ func (h *MessageHandler) adminGive(id string, session types.Session, payload *me
|
||||
ID: id,
|
||||
Target: payload.ID,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_LOCKED)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_GIVE)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -207,7 +207,7 @@ func (h *MessageHandler) adminMute(id string, session types.Session, payload *me
|
||||
Target: target.ID(),
|
||||
ID: id,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNMUTE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_MUTE)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"m1k1o/neko/internal/types/message"
|
||||
)
|
||||
|
||||
func (h *MessageHandler) boradcastCreate(session types.Session, payload *message.BroadcastCreate) error {
|
||||
func (h *MessageHandler) broadcastCreate(session types.Session, payload *message.BroadcastCreate) error {
|
||||
broadcast := h.capture.Broadcast()
|
||||
|
||||
if !session.Admin() {
|
||||
@ -44,14 +44,14 @@ func (h *MessageHandler) boradcastCreate(session types.Session, payload *message
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.boradcastStatus(nil); err != nil {
|
||||
if err := h.broadcastStatus(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MessageHandler) boradcastDestroy(session types.Session) error {
|
||||
func (h *MessageHandler) broadcastDestroy(session types.Session) error {
|
||||
broadcast := h.capture.Broadcast()
|
||||
|
||||
if !session.Admin() {
|
||||
@ -70,18 +70,18 @@ func (h *MessageHandler) boradcastDestroy(session types.Session) error {
|
||||
|
||||
broadcast.Stop()
|
||||
|
||||
if err := h.boradcastStatus(nil); err != nil {
|
||||
if err := h.broadcastStatus(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
||||
func (h *MessageHandler) broadcastStatus(session types.Session) error {
|
||||
broadcast := h.capture.Broadcast()
|
||||
|
||||
msg := message.BroadcastStatus{
|
||||
Event: event.BORADCAST_STATUS,
|
||||
Event: event.BROADCAST_STATUS,
|
||||
IsActive: broadcast.Started(),
|
||||
URL: broadcast.Url(),
|
||||
}
|
||||
@ -89,7 +89,7 @@ func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
||||
// if no session, broadcast change
|
||||
if session == nil {
|
||||
if err := h.sessions.AdminBroadcast(msg, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.BORADCAST_STATUS)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.BROADCAST_STATUS)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ func (h *MessageHandler) boradcastStatus(session types.Session) error {
|
||||
}
|
||||
|
||||
if err := session.Send(msg); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.BORADCAST_STATUS)
|
||||
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.BROADCAST_STATUS)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ func (h *MessageHandler) chat(id string, session types.Session, payload *message
|
||||
Content: payload.Content,
|
||||
ID: id,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_RELEASE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CHAT_MESSAGE)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -34,7 +34,7 @@ func (h *MessageHandler) chatEmote(id string, session types.Session, payload *me
|
||||
Emote: payload.Emote,
|
||||
ID: id,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_RELEASE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CHAT_EMOTE)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -115,7 +115,7 @@ func (h *MessageHandler) controlGive(id string, session types.Session, payload *
|
||||
ID: id,
|
||||
Target: payload.ID,
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_LOCKED)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_GIVE)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -148,15 +148,15 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
||||
return h.screenSet(id, session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
|
||||
// Boradcast Events
|
||||
case event.BORADCAST_CREATE:
|
||||
// Broadcast Events
|
||||
case event.BROADCAST_CREATE:
|
||||
payload := &message.BroadcastCreate{}
|
||||
return errors.Wrapf(
|
||||
utils.Unmarshal(payload, raw, func() error {
|
||||
return h.boradcastCreate(session, payload)
|
||||
return h.broadcastCreate(session, payload)
|
||||
}), "%s failed", header.Event)
|
||||
case event.BORADCAST_DESTROY:
|
||||
return errors.Wrapf(h.boradcastDestroy(session), "%s failed", header.Event)
|
||||
case event.BROADCAST_DESTROY:
|
||||
return errors.Wrapf(h.broadcastDestroy(session), "%s failed", header.Event)
|
||||
|
||||
// Admin Events
|
||||
case event.ADMIN_LOCK:
|
||||
|
@ -30,7 +30,7 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
|
||||
}
|
||||
|
||||
// send broadcast status if admin
|
||||
if err := h.boradcastStatus(session); err != nil {
|
||||
if err := h.broadcastStatus(session); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ func (h *MessageHandler) SessionConnected(id string, session types.Session) erro
|
||||
Event: event.MEMBER_CONNECTED,
|
||||
Member: session.Member(),
|
||||
}, nil); err != nil {
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.CONTROL_RELEASE)
|
||||
h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.MEMBER_CONNECTED)
|
||||
return err
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user