diff --git a/internal/routes/stream.go b/internal/routes/stream.go index ca1fa6bd..c8289712 100644 --- a/internal/routes/stream.go +++ b/internal/routes/stream.go @@ -132,7 +132,9 @@ func getStreamRoute(ctx *gin.Context) { } defer pipe.Close() if _, err := io.CopyN(w, pipe, contentLength); err != nil { - log.Error("Error while copying stream", zap.Error(err)) + if !utils.IsClientDisconnectError(err) { + log.Error("Error while copying stream", zap.Error(err)) + } } } } diff --git a/internal/utils/helpers.go b/internal/utils/helpers.go index d3319bf5..8b177b23 100644 --- a/internal/utils/helpers.go +++ b/internal/utils/helpers.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "math/rand" + "strings" "github.com/celestix/gotgproto" "github.com/celestix/gotgproto/ext" @@ -26,6 +27,22 @@ func Contains[T comparable](s []T, e T) bool { return false } +// IsClientDisconnectError checks if the error is due to client disconnecting +// e.g. user seeking in video, stopping playback, or network issues on client side +func IsClientDisconnectError(err error) bool { + if err == nil { + return false + } + errStr := err.Error() + return strings.Contains(errStr, "connection was aborted") || + strings.Contains(errStr, "connection reset by peer") || + strings.Contains(errStr, "broken pipe") || + strings.Contains(errStr, "forcibly closed") +} + +// telegram helper functions +// TODO: move these to a separate package if they grow too large + func GetTGMessage(ctx context.Context, client *gotgproto.Client, messageID int) (*tg.Message, error) { inputMessageID := tg.InputMessageClass(&tg.InputMessageID{ID: messageID}) channel, err := GetLogChannelPeer(ctx, client.API(), client.PeerStorage)