* feat(admin): identify Android devices by model in live session view Android clients that send a bare default User-Agent (e.g. "Dalvik/2.1.0 (Linux; U; Android 11; AFTKRT Build/RS8180.3729N)") showed up as "Dalvik" in the admin live-session view, which tells an operator nothing about the device. Parse the model code out of the UA (the token between the last ';' and "Build/") and map the Amazon Fire TV family and NVIDIA Shield to product names. Unknown but parseable models fall back to "Android · <MODEL>" instead of "Dalvik"; multi-word models like "Pixel 7" are preserved whole. This is display-only: the session still stores the raw model code in its user agent, and no response field or contract changes. * feat(admin): mark Jellyfin-compat sessions with the JF pill by origin The admin "JF" pill was derived at read time by substring-matching a token list against the client name / user agent. A real Jellyfin client that authenticates through the compat surface but sends a bare User-Agent and no MediaBrowser client name (e.g. a Fire TV app) got no pill, even though it plainly came through the Jellyfin API. Stamp compat origin as immutable identity at session creation and carry it through to the admin view: - ClientInfo.IsCompat is set true in the jellycompat auth path; newSession copies it onto Session.IsJellyfinCompat. - The flag rides the durable RecipeCard (next to the client metadata that already exists so the pill survives reconstruction) and is restored in ReconstructSession, so a server restart keeps the pill. - buildLiveSessionSync -> worker.SessionSync -> a new compat_origin column on playback_sessions_sync (added migration); the reconciler upserts, reloads, and compares it so origin changes still publish and unchanged rows do not churn. - The handler ORs the stored origin with the existing name/UA heuristic, which stays as a fallback for rows written before this column existed. is_jellyfin_client keeps the same name and type on the wire; it is only sourced more accurately. * fix(admin): correct Android device labels --------- Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
46 lines
1.6 KiB
Go
46 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/Silo-Server/silo-server/internal/playback"
|
|
"github.com/Silo-Server/silo-server/internal/worker"
|
|
)
|
|
|
|
// buildLiveSessionSync converts an in-memory playback session into the shared
|
|
// live admin session snapshot. For live admin views, play_method tracks the
|
|
// current transport method rather than the preserved semantic/base method used
|
|
// by player-facing responses and playback history.
|
|
func buildLiveSessionSync(s *playback.Session, reportingNode string) worker.SessionSync {
|
|
if s == nil {
|
|
return worker.SessionSync{ReportingNode: reportingNode}
|
|
}
|
|
|
|
return worker.SessionSync{
|
|
SessionID: s.ID,
|
|
UserID: s.UserID,
|
|
ProfileID: s.ProfileID,
|
|
MediaFileID: s.MediaFileID,
|
|
RequestedMediaFileID: s.RequestedMediaFileID,
|
|
PlayMethod: string(s.PlayMethod),
|
|
ReportingNode: reportingNode,
|
|
ClientIP: s.ClientIP,
|
|
ClientName: s.ClientName,
|
|
ClientVersion: s.ClientVersion,
|
|
ClientUserAgent: s.ClientUserAgent,
|
|
AudioTrackIndex: s.AudioTrackIndex,
|
|
TranscodeAudio: s.TranscodeAudio,
|
|
StreamBitrateKbps: s.StreamBitrateKbps,
|
|
TranscodeNodeURL: s.TranscodeNodeURL,
|
|
TargetResolution: s.TargetResolution,
|
|
TargetVideoCodec: s.TargetVideoCodec,
|
|
TargetAudioCodec: s.TargetAudioCodec,
|
|
TargetBitrateKbps: s.TargetBitrateKbps,
|
|
TranscodeHWAccel: s.TranscodeHWAccel,
|
|
StartedAt: s.StartedAt,
|
|
UpdatedAt: s.UpdatedAt,
|
|
PositionSeconds: s.Position,
|
|
IsPaused: s.IsPaused,
|
|
HasWebSocket: s.HasWebSocket,
|
|
IsJellyfinCompat: s.IsJellyfinCompat,
|
|
}
|
|
}
|