Build and publish / Test and build (darwin) (push) Successful in 1m43s
Build and publish / Test and build (linux) (push) Successful in 2m30s
Build and publish / Test and build (windows) (push) Successful in 3m5s
Build and publish / Build and publish Docker image (push) Successful in 1m46s
Implements the three roadmap feature sections:
- Browse: retrieve_item_children, retrieve_season_list, retrieve_episode_list;
search_for_item gains item_types (e.g. "Series") so shows are findable
without a selected library. Items now expose item_type, series_name, and
per-user played state/resume position.
- retrieve_next_episode: next_unplayed (Emby NextUp, resume counts) or latest
(most recent present episode; virtual placeholders skipped).
- retrieve_now_playing + set_subtitle/set_audio_track: stream listing and
track switching via SetSubtitleStreamIndex/SetAudioStreamIndex GeneralCommands,
with a PlayNow restart-at-position fallback for sessions that cannot switch
mid-play (DLNA/Chromecast bridges).
Conformance manifest grows to 34 call sites with three new documented spec
gaps (StartPositionTicks on PlayRequest, missing /Shows/{Id}/Episodes response
schema, undeclared Fields/EnableUserData on /Users/{UserId}/Items/{Id}).
180 lines
6.7 KiB
Go
180 lines
6.7 KiB
Go
package emby
|
|
|
|
// DTOs mirroring the Emby REST API JSON payloads (PascalCase field names).
|
|
// Only the fields used by Emby.MCP are modelled.
|
|
|
|
// QueryResult is Emby's standard paged list envelope.
|
|
type QueryResult[T any] struct {
|
|
Items []T `json:"Items"`
|
|
TotalRecordCount int `json:"TotalRecordCount"`
|
|
StartIndex int `json:"StartIndex"`
|
|
}
|
|
|
|
// authRequest is the body of POST /Users/AuthenticateByName.
|
|
type authRequest struct {
|
|
Username string `json:"Username"`
|
|
Pw string `json:"Pw"`
|
|
}
|
|
|
|
// AuthResult is the response of POST /Users/AuthenticateByName.
|
|
type AuthResult struct {
|
|
AccessToken string `json:"AccessToken"`
|
|
User *UserDto `json:"User"`
|
|
SessionInfo any `json:"SessionInfo"`
|
|
ServerID string `json:"ServerId"`
|
|
}
|
|
|
|
// UserDto is an Emby user (subset).
|
|
type UserDto struct {
|
|
ID string `json:"Id"`
|
|
Name string `json:"Name"`
|
|
}
|
|
|
|
// UserItemAccessEntry is one row of GET /Users/ItemAccess.
|
|
type UserItemAccessEntry struct {
|
|
ID string `json:"Id"`
|
|
Name string `json:"Name"`
|
|
UserItemShareLevel string `json:"UserItemShareLevel"`
|
|
}
|
|
|
|
// LibraryFolder is one item of GET /Library/MediaFolders.
|
|
type LibraryFolder struct {
|
|
Name string `json:"Name"`
|
|
Type string `json:"Type"`
|
|
CollectionType string `json:"CollectionType"`
|
|
ID string `json:"Id"`
|
|
}
|
|
|
|
// GenreItem is one item of GET /Genres.
|
|
type GenreItem struct {
|
|
Name string `json:"Name"`
|
|
}
|
|
|
|
// UserItemDataDto is the per-user state on a BaseItemDto (subset).
|
|
type UserItemDataDto struct {
|
|
Played bool `json:"Played"`
|
|
PlayedPercentage *float64 `json:"PlayedPercentage"`
|
|
PlaybackPositionTicks int64 `json:"PlaybackPositionTicks"`
|
|
IsFavorite bool `json:"IsFavorite"`
|
|
PlayCount int `json:"PlayCount"`
|
|
}
|
|
|
|
// MediaStream is one stream inside a MediaSource.
|
|
type MediaStream struct {
|
|
Index int `json:"Index"`
|
|
Type string `json:"Type"`
|
|
Language string `json:"Language"`
|
|
Codec string `json:"Codec"`
|
|
Title string `json:"Title"`
|
|
DisplayTitle string `json:"DisplayTitle"`
|
|
IsDefault bool `json:"IsDefault"`
|
|
IsForced bool `json:"IsForced"`
|
|
IsExternal bool `json:"IsExternal"`
|
|
IsTextSubtitleStream bool `json:"IsTextSubtitleStream"`
|
|
ExtraData string `json:"ExtraData"`
|
|
}
|
|
|
|
// MediaSource is one media source of a BaseItemDto.
|
|
type MediaSource struct {
|
|
MediaStreams []MediaStream `json:"MediaStreams"`
|
|
}
|
|
|
|
// BaseItemDto is the subset of Emby's BaseItemDto we consume.
|
|
type BaseItemDto struct {
|
|
ID string `json:"Id"`
|
|
Name string `json:"Name"`
|
|
Type string `json:"Type"`
|
|
CollectionType string `json:"CollectionType"`
|
|
Album string `json:"Album"`
|
|
AlbumID string `json:"AlbumId"`
|
|
AlbumArtist string `json:"AlbumArtist"`
|
|
Artists []string `json:"Artists"`
|
|
ParentIndexNumber *int `json:"ParentIndexNumber"`
|
|
IndexNumber *int `json:"IndexNumber"`
|
|
DateCreated string `json:"DateCreated"`
|
|
PremiereDate string `json:"PremiereDate"`
|
|
ProductionYear *int `json:"ProductionYear"`
|
|
Genres []string `json:"Genres"`
|
|
Overview string `json:"Overview"`
|
|
MediaSources []MediaSource `json:"MediaSources"`
|
|
MediaType string `json:"MediaType"`
|
|
RunTimeTicks int64 `json:"RunTimeTicks"`
|
|
Bitrate *int `json:"Bitrate"`
|
|
Path string `json:"Path"`
|
|
PlaylistItemID string `json:"PlaylistItemId"`
|
|
SeriesName string `json:"SeriesName"`
|
|
SeriesID string `json:"SeriesId"`
|
|
SeasonID string `json:"SeasonId"`
|
|
SeasonName string `json:"SeasonName"`
|
|
LocationType string `json:"LocationType"`
|
|
UserData *UserItemDataDto `json:"UserData"`
|
|
}
|
|
|
|
// PlayState is the playback state embedded in a session.
|
|
type PlayState struct {
|
|
PositionTicks int64 `json:"PositionTicks"`
|
|
IsPaused bool `json:"IsPaused"`
|
|
CanSeek bool `json:"CanSeek"`
|
|
AudioStreamIndex *int `json:"AudioStreamIndex"`
|
|
SubtitleStreamIndex *int `json:"SubtitleStreamIndex"`
|
|
MediaSourceID string `json:"MediaSourceId"`
|
|
}
|
|
|
|
// SessionInfo is an Emby session (subset).
|
|
type SessionInfo struct {
|
|
Client string `json:"Client"`
|
|
ID string `json:"Id"`
|
|
DeviceID string `json:"DeviceId"`
|
|
DeviceName string `json:"DeviceName"`
|
|
UserID string `json:"UserId"`
|
|
UserName string `json:"UserName"`
|
|
RemoteEndPoint string `json:"RemoteEndPoint"`
|
|
PlayableMediaTypes []string `json:"PlayableMediaTypes"`
|
|
SupportedCommands []string `json:"SupportedCommands"`
|
|
NowPlayingItem *BaseItemDto `json:"NowPlayingItem"`
|
|
PlayState *PlayState `json:"PlayState"`
|
|
}
|
|
|
|
// GeneralCommand is the body of POST /Sessions/{Id}/Command.
|
|
type GeneralCommand struct {
|
|
Name string `json:"Name"`
|
|
ControllingUserID string `json:"ControllingUserId,omitempty"`
|
|
Arguments map[string]string `json:"Arguments,omitempty"`
|
|
}
|
|
|
|
// playlistCreateResult is the response of POST /Playlists.
|
|
type playlistCreateResult struct {
|
|
ID string `json:"Id"`
|
|
}
|
|
|
|
// playlistAddResult is the response of POST /Playlists/{id}/Items.
|
|
type playlistAddResult struct {
|
|
ItemAddedCount int `json:"ItemAddedCount"`
|
|
}
|
|
|
|
// playRequest is the body of POST /Sessions/{id}/Playing.
|
|
// StartPositionTicks is sent although the OpenAPI PlayRequest schema omits it
|
|
// (spec gap — real Emby accepts it for resume-position restarts).
|
|
type playRequest struct {
|
|
ItemIDs []string `json:"ItemIds,omitempty"`
|
|
PlayCommand string `json:"PlayCommand,omitempty"`
|
|
ControllingUserID string `json:"ControllingUserId,omitempty"`
|
|
SubtitleStreamIndex *int `json:"SubtitleStreamIndex,omitempty"`
|
|
AudioStreamIndex *int `json:"AudioStreamIndex,omitempty"`
|
|
StartPositionTicks *int64 `json:"StartPositionTicks,omitempty"`
|
|
}
|
|
|
|
// playstateRequest is the body of POST /Sessions/{id}/Playing/{command}.
|
|
type playstateRequest struct {
|
|
Command string `json:"Command"`
|
|
SeekPositionTicks int64 `json:"SeekPositionTicks,omitempty"`
|
|
ControllingUserID string `json:"ControllingUserId,omitempty"`
|
|
}
|
|
|
|
// updateUserItemAccess is the body of POST /Items/Access.
|
|
type updateUserItemAccess struct {
|
|
ItemIDs []string `json:"ItemIds"`
|
|
UserIDs []string `json:"UserIds"`
|
|
ItemAccess string `json:"ItemAccess"`
|
|
}
|