diff --git a/dlna/dlna.go b/dlna/dlna.go
index de098b5..211a7a0 100644
--- a/dlna/dlna.go
+++ b/dlna/dlna.go
@@ -32,7 +32,7 @@ func BinaryInt(b bool) uint {
// "DLNA.ORG_OP=" time-seek-range-supp bytes-range-header-supp
func (cf ContentFeatures) String() (ret string) {
// DLNA.ORG_PN=[a-zA-Z0-9_]*
- params := make([]string, 0, 2)
+ params := make([]string, 0, 3)
if cf.ProfileName != "" {
params = append(params, "DLNA.ORG_PN="+cf.ProfileName)
}
@@ -41,6 +41,7 @@ func (cf ContentFeatures) String() (ret string) {
BinaryInt(cf.SupportTimeSeek),
BinaryInt(cf.SupportRange),
BinaryInt(cf.Transcoded)))
+ params = append(params, "DLNA.ORG_FLAGS=01700000000000000000000000000000")
return strings.Join(params, ";")
}
diff --git a/dlna/dlna_test.go b/dlna/dlna_test.go
index d149536..33b53f7 100644
--- a/dlna/dlna_test.go
+++ b/dlna/dlna_test.go
@@ -9,7 +9,7 @@ func TestContentFeaturesString(t *testing.T) {
Transcoded: true,
SupportTimeSeek: true,
}.String()
- e := "DLNA.ORG_OP=10;DLNA.ORG_CI=1"
+ e := "DLNA.ORG_OP=10;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=01700000000000000000000000000000"
if e != a {
t.Fatal(a)
}
diff --git a/dlna/dms/cd-service-desc.go b/dlna/dms/cd-service-desc.go
index c171cc4..4bf16cd 100644
--- a/dlna/dms/cd-service-desc.go
+++ b/dlna/dms/cd-service-desc.go
@@ -2,450 +2,505 @@ package dms
const contentDirectoryServiceDescription = `
-
- 1
- 0
-
-
-
- GetSearchCapabilities
-
-
- SearchCaps
- out
- SearchCapabilities
-
-
-
-
- GetSortCapabilities
-
-
- SortCaps
- out
- SortCapabilities
-
-
-
-
- GetSortExtensionCapabilities
-
-
- SortExtensionCaps
- out
- SortExtensionCapabilities
-
-
-
-
- GetFeatureList
-
-
- FeatureList
- out
- FeatureList
-
-
-
-
- GetSystemUpdateID
-
-
- Id
- out
- SystemUpdateID
-
-
-
-
- Browse
-
-
- ObjectID
- in
- A_ARG_TYPE_ObjectID
-
-
- BrowseFlag
- in
- A_ARG_TYPE_BrowseFlag
-
-
- Filter
- in
- A_ARG_TYPE_Filter
-
-
- StartingIndex
- in
- A_ARG_TYPE_Index
-
-
- RequestedCount
- in
- A_ARG_TYPE_Count
-
-
- SortCriteria
- in
- A_ARG_TYPE_SortCriteria
-
-
- Result
- out
- A_ARG_TYPE_Result
-
-
- NumberReturned
- out
- A_ARG_TYPE_Count
-
-
- TotalMatches
- out
- A_ARG_TYPE_Count
-
-
- UpdateID
- out
- A_ARG_TYPE_UpdateID
-
-
-
-
- Search
-
-
- ContainerID
- in
- A_ARG_TYPE_ObjectID
-
-
- SearchCriteria
- in
- A_ARG_TYPE_SearchCriteria
-
-
- Filter
- in
- A_ARG_TYPE_Filter
-
-
- StartingIndex
- in
- A_ARG_TYPE_Index
-
-
- RequestedCount
- in
- A_ARG_TYPE_Count
-
-
- SortCriteria
- in
- A_ARG_TYPE_SortCriteria
-
-
- Result
- out
- A_ARG_TYPE_Result
-
-
- NumberReturned
- out
- A_ARG_TYPE_Count
-
-
- TotalMatches
- out
- A_ARG_TYPE_Count
-
-
- UpdateID
- out
- A_ARG_TYPE_UpdateID
-
-
-
-
- CreateObject
-
-
- ContainerID
- in
- A_ARG_TYPE_ObjectID
-
-
- Elements
- in
- A_ARG_TYPE_Result
-
-
- ObjectID
- out
- A_ARG_TYPE_ObjectID
-
-
- Result
- out
- A_ARG_TYPE_Result
-
-
-
-
- DestroyObject
-
-
- ObjectID
- in
- A_ARG_TYPE_ObjectID
-
-
-
-
- UpdateObject
-
-
- ObjectID
- in
- A_ARG_TYPE_ObjectID
-
-
- CurrentTagValue
- in
- A_ARG_TYPE_TagValueList
-
-
- NewTagValue
- in
- A_ARG_TYPE_TagValueList
-
-
-
-
- MoveObject
-
-
- ObjectID
- in
- A_ARG_TYPE_ObjectID
-
-
- NewParentID
- in
- A_ARG_TYPE_ObjectID
-
-
- NewObjectID
- out
- A_ARG_TYPE_ObjectID
-
-
-
-
- ImportResource
-
-
- SourceURI
- in
- A_ARG_TYPE_URI
-
-
- DestinationURI
- in
- A_ARG_TYPE_URI
-
-
- TransferID
- out
- A_ARG_TYPE_TransferID
-
-
-
-
- ExportResource
-
-
- SourceURI
- in
- A_ARG_TYPE_URI
-
-
- DestinationURI
- in
- A_ARG_TYPE_URI
-
-
- TransferID
- out
- A_ARG_TYPE_TransferID
-
-
-
-
- StopTransferResource
-
-
- TransferID
- in
- A_ARG_TYPE_TransferID
-
-
-
-
- DeleteResource
-
-
- ResourceURI
- in
- A_ARG_TYPE_URI
-
-
-
-
- GetTransferProgress
-
-
- TransferID
- in
- A_ARG_TYPE_TransferID
-
-
- TransferStatus
- out
- A_ARG_TYPE_TransferStatus
-
-
- TransferLength
- out
- A_ARG_TYPE_TransferLength
-
-
- TransferTotal
- out
- A_ARG_TYPE_TransferTotal
-
-
-
-
- CreateReference
-
-
- ContainerID
- in
- A_ARG_TYPE_ObjectID
-
-
- ObjectID
- in
- A_ARG_TYPE_ObjectID
-
-
- NewID
- out
- A_ARG_TYPE_ObjectID
-
-
-
-
-
-
- SearchCapabilities
- string
-
-
- SortCapabilities
- string
-
-
- SortExtensionCapabilities
- string
-
-
- SystemUpdateID
- ui4
-
-
- ContainerUpdateIDs
- string
-
-
- TransferIDs
- string
-
-
- FeatureList
- string
-
-
- A_ARG_TYPE_ObjectID
- string
-
-
- A_ARG_TYPE_Result
- string
-
-
- A_ARG_TYPE_SearchCriteria
- string
-
-
- A_ARG_TYPE_BrowseFlag
- string
-
- BrowseMetadata
- BrowseDirectChildren
-
-
-
- A_ARG_TYPE_Filter
- string
-
-
- A_ARG_TYPE_SortCriteria
- string
-
-
- A_ARG_TYPE_Index
- ui4
-
-
- A_ARG_TYPE_Count
- ui4
-
-
- A_ARG_TYPE_UpdateID
- ui4
-
-
- A_ARG_TYPE_TransferID
- ui4
-
-
- A_ARG_TYPE_TransferStatus
- string
-
- COMPLETED
- ERROR
- IN_PROGRESS
- STOPPED
-
-
-
- A_ARG_TYPE_TransferLength
- string
-
-
- A_ARG_TYPE_TransferTotal
- string
-
-
- A_ARG_TYPE_TagValueList
- string
-
-
- A_ARG_TYPE_URI
- uri
-
-
+
+ 1
+ 0
+
+
+
+ GetSearchCapabilities
+
+
+ SearchCaps
+ out
+ SearchCapabilities
+
+
+
+
+ GetSortCapabilities
+
+
+ SortCaps
+ out
+ SortCapabilities
+
+
+
+
+ GetSortExtensionCapabilities
+
+
+ SortExtensionCaps
+ out
+ SortExtensionCapabilities
+
+
+
+
+ GetFeatureList
+
+
+ FeatureList
+ out
+ FeatureList
+
+
+
+
+ GetSystemUpdateID
+
+
+ Id
+ out
+ SystemUpdateID
+
+
+
+
+ Browse
+
+
+ ObjectID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ BrowseFlag
+ in
+ A_ARG_TYPE_BrowseFlag
+
+
+ Filter
+ in
+ A_ARG_TYPE_Filter
+
+
+ StartingIndex
+ in
+ A_ARG_TYPE_Index
+
+
+ RequestedCount
+ in
+ A_ARG_TYPE_Count
+
+
+ SortCriteria
+ in
+ A_ARG_TYPE_SortCriteria
+
+
+ Result
+ out
+ A_ARG_TYPE_Result
+
+
+ NumberReturned
+ out
+ A_ARG_TYPE_Count
+
+
+ TotalMatches
+ out
+ A_ARG_TYPE_Count
+
+
+ UpdateID
+ out
+ A_ARG_TYPE_UpdateID
+
+
+
+
+ Search
+
+
+ ContainerID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ SearchCriteria
+ in
+ A_ARG_TYPE_SearchCriteria
+
+
+ Filter
+ in
+ A_ARG_TYPE_Filter
+
+
+ StartingIndex
+ in
+ A_ARG_TYPE_Index
+
+
+ RequestedCount
+ in
+ A_ARG_TYPE_Count
+
+
+ SortCriteria
+ in
+ A_ARG_TYPE_SortCriteria
+
+
+ Result
+ out
+ A_ARG_TYPE_Result
+
+
+ NumberReturned
+ out
+ A_ARG_TYPE_Count
+
+
+ TotalMatches
+ out
+ A_ARG_TYPE_Count
+
+
+ UpdateID
+ out
+ A_ARG_TYPE_UpdateID
+
+
+
+
+ CreateObject
+
+
+ ContainerID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ Elements
+ in
+ A_ARG_TYPE_Result
+
+
+ ObjectID
+ out
+ A_ARG_TYPE_ObjectID
+
+
+ Result
+ out
+ A_ARG_TYPE_Result
+
+
+
+
+ DestroyObject
+
+
+ ObjectID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+
+
+ UpdateObject
+
+
+ ObjectID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ CurrentTagValue
+ in
+ A_ARG_TYPE_TagValueList
+
+
+ NewTagValue
+ in
+ A_ARG_TYPE_TagValueList
+
+
+
+
+ MoveObject
+
+
+ ObjectID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ NewParentID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ NewObjectID
+ out
+ A_ARG_TYPE_ObjectID
+
+
+
+
+ ImportResource
+
+
+ SourceURI
+ in
+ A_ARG_TYPE_URI
+
+
+ DestinationURI
+ in
+ A_ARG_TYPE_URI
+
+
+ TransferID
+ out
+ A_ARG_TYPE_TransferID
+
+
+
+
+ ExportResource
+
+
+ SourceURI
+ in
+ A_ARG_TYPE_URI
+
+
+ DestinationURI
+ in
+ A_ARG_TYPE_URI
+
+
+ TransferID
+ out
+ A_ARG_TYPE_TransferID
+
+
+
+
+ StopTransferResource
+
+
+ TransferID
+ in
+ A_ARG_TYPE_TransferID
+
+
+
+
+ DeleteResource
+
+
+ ResourceURI
+ in
+ A_ARG_TYPE_URI
+
+
+
+
+ GetTransferProgress
+
+
+ TransferID
+ in
+ A_ARG_TYPE_TransferID
+
+
+ TransferStatus
+ out
+ A_ARG_TYPE_TransferStatus
+
+
+ TransferLength
+ out
+ A_ARG_TYPE_TransferLength
+
+
+ TransferTotal
+ out
+ A_ARG_TYPE_TransferTotal
+
+
+
+
+ CreateReference
+
+
+ ContainerID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ ObjectID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ NewID
+ out
+ A_ARG_TYPE_ObjectID
+
+
+
+
+ X_GetFeatureList
+
+
+ FeatureList
+ out
+ A_ARG_TYPE_Featurelist
+
+
+
+
+ X_SetBookmark
+
+
+ CategoryType
+ in
+ A_ARG_TYPE_CategoryType
+
+
+ RID
+ in
+ A_ARG_TYPE_RID
+
+
+ ObjectID
+ in
+ A_ARG_TYPE_ObjectID
+
+
+ PosSecond
+ in
+ A_ARG_TYPE_PosSec
+
+
+
+
+
+
+ SearchCapabilities
+ string
+
+
+ SortCapabilities
+ string
+
+
+ SortExtensionCapabilities
+ string
+
+
+ SystemUpdateID
+ ui4
+
+
+ ContainerUpdateIDs
+ string
+
+
+ TransferIDs
+ string
+
+
+ FeatureList
+ string
+
+
+ A_ARG_TYPE_ObjectID
+ string
+
+
+ A_ARG_TYPE_Result
+ string
+
+
+ A_ARG_TYPE_SearchCriteria
+ string
+
+
+ A_ARG_TYPE_BrowseFlag
+ string
+
+ BrowseMetadata
+ BrowseDirectChildren
+
+
+
+ A_ARG_TYPE_Filter
+ string
+
+
+ A_ARG_TYPE_SortCriteria
+ string
+
+
+ A_ARG_TYPE_Index
+ ui4
+
+
+ A_ARG_TYPE_Count
+ ui4
+
+
+ A_ARG_TYPE_UpdateID
+ ui4
+
+
+ A_ARG_TYPE_TransferID
+ ui4
+
+
+ A_ARG_TYPE_TransferStatus
+ string
+
+ COMPLETED
+ ERROR
+ IN_PROGRESS
+ STOPPED
+
+
+
+ A_ARG_TYPE_TransferLength
+ string
+
+
+ A_ARG_TYPE_TransferTotal
+ string
+
+
+ A_ARG_TYPE_TagValueList
+ string
+
+
+ A_ARG_TYPE_URI
+ uri
+
+
+ A_ARG_TYPE_CategoryType
+ ui4
+
+
+
+ A_ARG_TYPE_RID
+ ui4
+
+
+
+ A_ARG_TYPE_PosSec
+ ui4
+
+
+
+ A_ARG_TYPE_Featurelist
+ string
+
+
+
`
diff --git a/dlna/dms/cds.go b/dlna/dms/cds.go
index b394c85..b408d3f 100644
--- a/dlna/dms/cds.go
+++ b/dlna/dms/cds.go
@@ -287,6 +287,21 @@ func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http
return [][2]string{
{"SearchCaps", ""},
}, nil
+ // Samsung Extensions
+ case "X_GetFeatureList":
+ // TODO: make it dependable on model
+ // https://github.com/1100101/minidlna/blob/ca6dbba18390ad6f8b8d7b7dbcf797dbfd95e2db/upnpsoap.c#L2153-L2199
+ return [][2]string{
+ {"FeatureList", `
+
+ // "A"
+ // "V"
+ // "I"
+
+`}}, nil
+ case "X_SetBookmark":
+ // just ignore
+ return [][2]string{}, nil
default:
return nil, upnp.InvalidActionError
}
diff --git a/dlna/dms/cm-service-desc.go b/dlna/dms/cm-service-desc.go
index 855d19c..f9cf9ae 100644
--- a/dlna/dms/cm-service-desc.go
+++ b/dlna/dms/cm-service-desc.go
@@ -1,275 +1,185 @@
package dms
-const connectionManagerServiceDesc = `
+const connectionManagerServiceDescription = `
-
- 1
- 0
-
-
-
- GetProtocolInfo
-
-
- Source
- out
-
-SourceProtocolInfo
-
-
-
- Sink
- out
-
-SinkProtocolInfo
-
-
-
-
-
- PrepareForConnection
-
-
- RemoteProtocolInfo
- in
-
-A_ARG_TYPE_ProtocolInfo
-
-
-
- PeerConnectionManager
- in
-
-A_ARG_TYPE_ConnectionManager
-
-
-
- PeerConnectionID
- in
-
-A_ARG_TYPE_ConnectionID
-
-
-
- Direction
- in
-
-A_ARG_TYPE_Direction
-
-
-
- ConnectionID
- out
-
-A_ARG_TYPE_ConnectionID
-
-
-
- AVTransportID
- out
- A_ARG_TYPE_AVTransportID
-
-
- RcsID
- out
- A_ARG_TYPE_RcsID
-
-
-
-
- ConnectionComplete
-
-
- ConnectionID
- in
- A_ARG_TYPE_ConnectionID
-
-
-
-
- GetCurrentConnectionIDs
-
-
- ConnectionIDs
- out
-
-CurrentConnectionIDs
-
-
-
-
-
- GetCurrentConnectionInfo
-
-
- ConnectionID
- in
-
-A_ARG_TYPE_ConnectionID
-
-
-
- RcsID
- out
-
-A_ARG_TYPE_RcsID
-
-
-
- AVTransportID
- out
-
-A_ARG_TYPE_AVTransportID
-
-
-
- ProtocolInfo
- out
-
-A_ARG_TYPE_ProtocolInfo
-
-
-
- PeerConnectionManager
- out
-
-A_ARG_TYPE_ConnectionManager
-
-
-
- PeerConnectionID
- out
-
-A_ARG_TYPE_ConnectionID
-
-
-
- Direction
- out
-
-A_ARG_TYPE_Direction
-
-
-
- Status
- out
-
-A_ARG_TYPE_ConnectionStatus
-
-
-
-
-
- GetRendererItemInfo
-
-
- ItemInfoFilter
- in
-
-A_ARG_TYPE_ItemInfoFilter
-
-
-
- ItemMetadataList
- in
-
-A_ARG_TYPE_Result
-
-
-
- ItemRenderingInfoList
- out
-
-A_ARG_TYPE_RenderingInfoList
-
-
-
-
-
- GetFeatureList
-
-
- FeatureList
- out
-
-FeatureList
-
-
-
-
-
-
-
- SourceProtocolInfo
- string
-
-
- SinkProtocolInfo
- string
-
-
- CurrentConnectionIDs
- string
-
-
- A_ARG_TYPE_ConnectionStatus
- string
-
- OK
- ContentFormatMismatch
- InsufficientBandwidth
- UnreliableChannel
- Unknown
-
-
-
- A_ARG_TYPE_ConnectionManager
- string
-
-
- A_ARG_TYPE_Direction
- string
-
- Input
- Output
-
-
-
- A_ARG_TYPE_ProtocolInfo
- string
-
-
- A_ARG_TYPE_ConnectionID
- i4
-
-
- A_ARG_TYPE_AVTransportID
- i4
-
-
- A_ARG_TYPE_RcsID
- i4
-
-
- A_ARG_TYPE_ItemInfoFilter
- string
-
-
- A_ARG_TYPE_Result
- string
-
-
- A_ARG_TYPE_RenderingInfoList
- string
-
-
- ClockUpdateID
- ui4
-
-
- DeviceClockInfoUpdates
- string
-
-
+
+ 1
+ 0
+
+
+
+ GetProtocolInfo
+
+
+ Source
+ out
+ SourceProtocolInfo
+
+
+ Sink
+ out
+ SinkProtocolInfo
+
+
+
+
+ PrepareForConnection
+
+
+ RemoteProtocolInfo
+ in
+ A_ARG_TYPE_ProtocolInfo
+
+
+ PeerConnectionManager
+ in
+ A_ARG_TYPE_ConnectionManager
+
+
+ PeerConnectionID
+ in
+ A_ARG_TYPE_ConnectionID
+
+
+ Direction
+ in
+ A_ARG_TYPE_Direction
+
+
+ ConnectionID
+ out
+ A_ARG_TYPE_ConnectionID
+
+
+ AVTransportID
+ out
+ A_ARG_TYPE_AVTransportID
+
+
+ RcsID
+ out
+ A_ARG_TYPE_RcsID
+
+
+
+
+ ConnectionComplete
+
+
+ ConnectionID
+ in
+ A_ARG_TYPE_ConnectionID
+
+
+
+
+ GetCurrentConnectionIDs
+
+
+ ConnectionIDs
+ out
+ CurrentConnectionIDs
+
+
+
+
+ GetCurrentConnectionInfo
+
+
+ ConnectionID
+ in
+ A_ARG_TYPE_ConnectionID
+
+
+ RcsID
+ out
+ A_ARG_TYPE_RcsID
+
+
+ AVTransportID
+ out
+ A_ARG_TYPE_AVTransportID
+
+
+ ProtocolInfo
+ out
+ A_ARG_TYPE_ProtocolInfo
+
+
+ PeerConnectionManager
+ out
+ A_ARG_TYPE_ConnectionManager
+
+
+ PeerConnectionID
+ out
+ A_ARG_TYPE_ConnectionID
+
+
+ Direction
+ out
+ A_ARG_TYPE_Direction
+
+
+ Status
+ out
+ A_ARG_TYPE_ConnectionStatus
+
+
+
+
+
+
+ SourceProtocolInfo
+ string
+
+
+ SinkProtocolInfo
+ string
+
+
+ CurrentConnectionIDs
+ string
+
+
+ A_ARG_TYPE_ConnectionStatus
+ string
+
+ OK
+ ContentFormatMismatch
+ InsufficientBandwidth
+ UnreliableChannel
+ Unknown
+
+
+
+ A_ARG_TYPE_ConnectionManager
+ string
+
+
+ A_ARG_TYPE_Direction
+ string
+
+ Input
+ Output
+
+
+
+ A_ARG_TYPE_ProtocolInfo
+ string
+
+
+ A_ARG_TYPE_ConnectionID
+ i4
+
+
+ A_ARG_TYPE_AVTransportID
+ i4
+
+
+ A_ARG_TYPE_RcsID
+ i4
+
+
`
diff --git a/dlna/dms/cms.go b/dlna/dms/cms.go
new file mode 100644
index 0000000..caacc6f
--- /dev/null
+++ b/dlna/dms/cms.go
@@ -0,0 +1,42 @@
+package dms
+
+import (
+ "net/http"
+
+ "github.com/anacrolix/dms/upnp"
+)
+
+//const defaultProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*"
+const defaultProtocolInfo = "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_RES_H_V,http-get:*:image/png:DLNA.ORG_PN=PNG_TN,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3,http-get:*:audio/L16:DLNA.ORG_PN=LPCM,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_24_AC3_ISO;SONY.COM_PN=AVC_TS_HD_24_AC3_ISO,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_24_AC3;SONY.COM_PN=AVC_TS_HD_24_AC3,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_24_AC3_T;SONY.COM_PN=AVC_TS_HD_24_AC3_T,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_PS_PAL,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_PS_NTSC,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_50_L2_T,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_60_L2_T,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_50_AC3_T,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_50_L2_ISO;SONY.COM_PN=HD2_50_ISO,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_60_AC3_T,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_60_L2_ISO;SONY.COM_PN=HD2_60_ISO,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_HD_50_L2_T;SONY.COM_PN=HD2_50_T,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_HD_60_L2_T;SONY.COM_PN=HD2_60_T,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;SONY.COM_PN=AVC_TS_HD_50_AC3_ISO,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;SONY.COM_PN=AVC_TS_HD_50_AC3,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_60_AC3_ISO;SONY.COM_PN=AVC_TS_HD_60_AC3_ISO,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_60_AC3;SONY.COM_PN=AVC_TS_HD_60_AC3,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;SONY.COM_PN=AVC_TS_HD_50_AC3_T,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_60_AC3_T;SONY.COM_PN=AVC_TS_HD_60_AC3_T,http-get:*:video/x-mp2t-mphl-188:*,http-get:*:video/*:*,http-get:*:audio/*:*,http-get:*:image/*:*,http-get:*:text/srt:*,http-get:*:text/smi:*,http-get:*:text/ssa:*,http-get:*:*:*"
+
+type connectionManagerService struct {
+ *Server
+ upnp.Eventing
+}
+
+func (cms *connectionManagerService) Handle(action string, argsXML []byte, r *http.Request) ([][2]string, error) {
+ switch action {
+ case ".GetCurrentConnectionInfo":
+ return [][2]string{
+ {"ConnectionID", "0"},
+ {"RcsID", "-1"},
+ {"AVTransportID", "-1"},
+ {"ProtocolInfo", ""},
+ {"PeerConnectionManager", ""},
+ {"PeerConnectionID", "-1"},
+ {"Direction", "Output"},
+ {"Status", "OK"},
+ }, nil
+ case "GetCurrentConnectionIDs":
+ return [][2]string{
+ {"ConnectionIDs", ""},
+ }, nil
+ case "GetProtocolInfo":
+ return [][2]string{
+ {"Source", defaultProtocolInfo},
+ {"Sink", ""},
+ }, nil
+ default:
+ return nil, upnp.InvalidActionError
+ }
+}
diff --git a/dlna/dms/dms.go b/dlna/dms/dms.go
index c4d7850..e2bc012 100644
--- a/dlna/dms/dms.go
+++ b/dlna/dms/dms.go
@@ -18,6 +18,7 @@ import (
"os/user"
"path"
"path/filepath"
+ "strconv"
"strings"
"time"
@@ -37,7 +38,6 @@ const (
resPath = "/res"
iconPath = "/icon"
rootDescPath = "/rootDesc.xml"
- contentDirectorySCPDURL = "/scpd/ContentDirectory.xml"
contentDirectoryEventSubURL = "/evt/ContentDirectory"
serviceControlURL = "/ctl"
deviceIconPath = "/deviceIcon"
@@ -85,13 +85,20 @@ var services = []*service{
},
SCPD: contentDirectoryServiceDescription,
},
- // {
- // Service: upnp.Service{
- // ServiceType: "urn:schemas-upnp-org:service:ConnectionManager:3",
- // ServiceId: "urn:upnp-org:serviceId:ConnectionManager",
- // },
- // SCPD: connectionManagerServiceDesc,
- // },
+ {
+ Service: upnp.Service{
+ ServiceType: "urn:schemas-upnp-org:service:ConnectionManager:1",
+ ServiceId: "urn:upnp-org:serviceId:ConnectionManager",
+ },
+ SCPD: connectionManagerServiceDescription,
+ },
+ {
+ Service: upnp.Service{
+ ServiceType: "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
+ ServiceId: "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar",
+ },
+ SCPD: mediaReceiverRegistrarDescription,
+ },
}
// The control URL for every service is the same. We're able to infer the desired service from the request headers.
@@ -487,8 +494,9 @@ func (me *mitmRespWriter) CloseNotify() <-chan bool {
// Set the SCPD serve paths.
func init() {
for _, s := range services {
- p := path.Join("/scpd", s.ServiceId)
- s.SCPDURL = p
+ lastInd := strings.LastIndex(s.ServiceId, ":")
+ p := path.Join("/scpd", s.ServiceId[lastInd+1:])
+ s.SCPDURL = p + ".xml"
}
}
@@ -498,7 +506,7 @@ func handleSCPDs(mux *http.ServeMux) {
mux.HandleFunc(s.SCPDURL, func(serviceDesc string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", `text/xml; charset="utf-8"`)
- http.ServeContent(w, r, ".xml", startTime, bytes.NewReader([]byte(serviceDesc)))
+ http.ServeContent(w, r, "", startTime, bytes.NewReader([]byte(serviceDesc)))
}
}(s.SCPD))
}
@@ -594,7 +602,10 @@ func (me *Server) serveIcon(w http.ResponseWriter, r *http.Request) {
// cmd.Stderr = os.Stderr
body, err := cmd.Output()
if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ // serve 1st Icon if no ffmpegthumbnailer
+ w.Header().Set("Content-Type", me.Icons[0].Mimetype)
+ http.ServeContent(w, r, "", time.Time{}, me.Icons[0].ReadSeeker)
+ //http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.ServeContent(w, r, "", time.Now(), bytes.NewReader(body))
@@ -771,11 +782,19 @@ func (server *Server) initMux(mux *http.ServeMux) {
handleSCPDs(mux)
mux.HandleFunc(serviceControlURL, server.serviceControlHandler)
mux.HandleFunc("/debug/pprof/", pprof.Index)
- for i, di := range server.Icons {
- mux.HandleFunc(fmt.Sprintf("%s/%d", deviceIconPath, i), func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", di.Mimetype)
- http.ServeContent(w, r, "", time.Time{}, di.ReadSeeker)
- })
+ // DeviceIcons
+ iconHandl := func(w http.ResponseWriter, r *http.Request) {
+ idStr := path.Base(r.URL.Path)
+ id, _ := strconv.Atoi(idStr)
+ if id < 0 || id >= len(server.Icons) {
+ id = 0
+ }
+ di := server.Icons[id]
+ w.Header().Set("Content-Type", di.Mimetype)
+ http.ServeContent(w, r, "", time.Time{}, di.ReadSeeker)
+ }
+ for i, _ := range server.Icons {
+ mux.HandleFunc(fmt.Sprintf("%s/%d", deviceIconPath, i), iconHandl)
}
}
@@ -784,10 +803,24 @@ func (s *Server) initServices() (err error) {
if err != nil {
return
}
+ urn1, err := upnp.ParseServiceType(services[1].ServiceType)
+ if err != nil {
+ return
+ }
+ urn2, err := upnp.ParseServiceType(services[2].ServiceType)
+ if err != nil {
+ return
+ }
s.services = map[string]UPnPService{
urn.Type: &contentDirectoryService{
Server: s,
},
+ urn1.Type: &connectionManagerService{
+ Server: s,
+ },
+ urn2.Type: &mediaReceiverRegistrarService{
+ Server: s,
+ },
}
return
}
@@ -827,6 +860,8 @@ func (srv *Server) Init() (err error) {
srv.rootDeviceUUID = makeDeviceUuid(srv.FriendlyName)
srv.rootDescXML, err = xml.MarshalIndent(
upnp.DeviceDesc{
+ NSDLNA: "urn:schemas-dlna-org:device-1-0",
+ NSSEC: "http://www.sec.co.kr/dlna",
SpecVersion: upnp.SpecVersion{Major: 1, Minor: 0},
Device: upnp.Device{
DeviceType: rootDeviceType,
@@ -834,6 +869,12 @@ func (srv *Server) Init() (err error) {
Manufacturer: "Matt Joiner ",
ModelName: rootDeviceModelName,
UDN: srv.rootDeviceUUID,
+ VendorXML: `
+
+ DMS-1.50
+ M-DMS-1.50
+ smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec
+ smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec`,
ServiceList: func() (ss []upnp.Service) {
for _, s := range services {
ss = append(ss, s.Service)
@@ -852,6 +893,7 @@ func (srv *Server) Init() (err error) {
}
return
}(),
+ PresentationURL: "/",
},
},
" ", " ")
diff --git a/dlna/dms/mrrs-desc.go b/dlna/dms/mrrs-desc.go
new file mode 100644
index 0000000..07fb0a9
--- /dev/null
+++ b/dlna/dms/mrrs-desc.go
@@ -0,0 +1,90 @@
+package dms
+
+const mediaReceiverRegistrarDescription = `
+
+
+ 1
+ 0
+
+
+
+ IsAuthorized
+
+
+ DeviceID
+ in
+ A_ARG_TYPE_DeviceID
+
+
+ Result
+ out
+ A_ARG_TYPE_Result
+
+
+
+
+ RegisterDevice
+
+
+ RegistrationReqMsg
+ in
+ A_ARG_TYPE_RegistrationReqMsg
+
+
+ RegistrationRespMsg
+ out
+ A_ARG_TYPE_RegistrationRespMsg
+
+
+
+
+ IsValidated
+
+
+ DeviceID
+ in
+ A_ARG_TYPE_DeviceID
+
+
+ Result
+ out
+ A_ARG_TYPE_Result
+
+
+
+
+
+
+ A_ARG_TYPE_DeviceID
+ string
+
+
+ A_ARG_TYPE_Result
+ int
+
+
+ A_ARG_TYPE_RegistrationReqMsg
+ bin.base64
+
+
+ A_ARG_TYPE_RegistrationRespMsg
+ bin.base64
+
+
+ AuthorizationGrantedUpdateID
+ ui4
+
+
+ AuthorizationDeniedUpdateID
+ ui4
+
+
+ ValidationSucceededUpdateID
+ ui4
+
+
+ ValidationRevokedUpdateID
+ ui4
+
+
+`
diff --git a/dlna/dms/mrrs.go b/dlna/dms/mrrs.go
new file mode 100644
index 0000000..08c8592
--- /dev/null
+++ b/dlna/dms/mrrs.go
@@ -0,0 +1,28 @@
+package dms
+
+import (
+ "net/http"
+
+ "github.com/anacrolix/dms/upnp"
+)
+
+type mediaReceiverRegistrarService struct {
+ *Server
+ upnp.Eventing
+}
+
+func (mrrs *mediaReceiverRegistrarService) Handle(action string, argsXML []byte, r *http.Request) ([][2]string, error) {
+ switch action {
+ case "IsAuthorized", "IsValidated":
+ return [][2]string{
+ {"Result", "1"},
+ }, nil
+ case "RegisterDevice":
+ return [][2]string{
+ {"RegistrationRespMsg", mrrs.rootDeviceUUID},
+ }, nil
+ // return nil, nil
+ default:
+ return nil, upnp.InvalidActionError
+ }
+}
diff --git a/ssdp/ssdp.go b/ssdp/ssdp.go
index b099ad0..b622aa8 100644
--- a/ssdp/ssdp.go
+++ b/ssdp/ssdp.go
@@ -106,7 +106,13 @@ func makeConn(ifi net.Interface) (ret *net.UDPConn, err error) {
func (me *Server) serve() {
for {
- b := make([]byte, me.Interface.MTU)
+ size := me.Interface.MTU
+ if size > 65536 {
+ size = 65536
+ } else if size <= 0 { // fix for windows with mtu 4gb
+ size = 65536
+ }
+ b := make([]byte, size)
n, addr, err := me.conn.ReadFromUDP(b)
select {
case <-me.closed:
diff --git a/upnp/upnp.go b/upnp/upnp.go
index eef9295..beea9e7 100644
--- a/upnp/upnp.go
+++ b/upnp/upnp.go
@@ -10,15 +10,16 @@ import (
"strings"
)
-var serviceURNRegexp *regexp.Regexp = regexp.MustCompile(`^urn:schemas-upnp-org:service:(\w+):(\d+)$`)
+var serviceURNRegexp *regexp.Regexp = regexp.MustCompile(`^urn:(.*):service:(\w+):(\d+)$`)
type ServiceURN struct {
+ Auth string
Type string
Version uint64
}
func (me ServiceURN) String() string {
- return fmt.Sprintf("urn:schemas-upnp-org:service:%s:%d", me.Type, me.Version)
+ return fmt.Sprintf("urn:%s:service:%s:%d", me.Auth, me.Type, me.Version)
}
func ParseServiceType(s string) (ret ServiceURN, err error) {
@@ -27,11 +28,12 @@ func ParseServiceType(s string) (ret ServiceURN, err error) {
err = errors.New(s)
return
}
- if len(matches) != 3 {
- log.Panicf("Invalid serviceURNRegexp ?")
+ if len(matches) != 4 {
+ log.Panicf("Invalid serviceURNRegexp?")
}
- ret.Type = matches[1]
- ret.Version, err = strconv.ParseUint(matches[2], 0, 0)
+ ret.Auth = matches[1]
+ ret.Type = matches[2]
+ ret.Version, err = strconv.ParseUint(matches[3], 0, 0)
return
}
@@ -80,17 +82,21 @@ type Service struct {
}
type Device struct {
- DeviceType string `xml:"deviceType"`
- FriendlyName string `xml:"friendlyName"`
- Manufacturer string `xml:"manufacturer"`
- ModelName string `xml:"modelName"`
- UDN string
- IconList []Icon `xml:"iconList>icon"`
- ServiceList []Service `xml:"serviceList>service"`
+ DeviceType string `xml:"deviceType"`
+ FriendlyName string `xml:"friendlyName"`
+ Manufacturer string `xml:"manufacturer"`
+ ModelName string `xml:"modelName"`
+ UDN string
+ VendorXML string `xml:",innerxml"`
+ IconList []Icon `xml:"iconList>icon"`
+ ServiceList []Service `xml:"serviceList>service"`
+ PresentationURL string `xml:"presentationURL,omitempty"`
}
type DeviceDesc struct {
XMLName xml.Name `xml:"urn:schemas-upnp-org:device-1-0 root"`
+ NSDLNA string `xml:"xmlns:dlna,attr"`
+ NSSEC string `xml:"xmlns:sec,attr"`
SpecVersion SpecVersion `xml:"specVersion"`
Device Device `xml:"device"`
}
diff --git a/upnpav/upnpav.go b/upnpav/upnpav.go
index b57b359..b19b686 100644
--- a/upnpav/upnpav.go
+++ b/upnpav/upnpav.go
@@ -2,12 +2,15 @@ package upnpav
import (
"encoding/xml"
+ "time"
)
const (
+ // NoSuchObjectErrorCode : The specified ObjectID is invalid.
NoSuchObjectErrorCode = 701
)
+// Resource description
type Resource struct {
XMLName xml.Name `xml:"res"`
ProtocolInfo string `xml:"protocolInfo,attr"`
@@ -18,28 +21,44 @@ type Resource struct {
Resolution string `xml:"resolution,attr,omitempty"`
}
+// Container description
type Container struct {
Object
XMLName xml.Name `xml:"container"`
ChildCount int `xml:"childCount,attr"`
}
+// Item description
type Item struct {
Object
- XMLName xml.Name `xml:"item"`
- Res []Resource
+ XMLName xml.Name `xml:"item"`
+ Res []Resource
+ InnerXML string `xml:",innerxml"`
}
+// Object description
type Object struct {
- ID string `xml:"id,attr"`
- ParentID string `xml:"parentID,attr"`
- Restricted int `xml:"restricted,attr"` // indicates whether the object is modifiable
- Title string `xml:"dc:title"`
- Class string `xml:"upnp:class"`
- Icon string `xml:"upnp:icon,omitempty"`
- Artist string `xml:"upnp:artist,omitempty"`
- Album string `xml:"upnp:album,omitempty"`
- Genre string `xml:"upnp:genre,omitempty"`
- AlbumArtURI string `xml:"upnp:albumArtURI,omitempty"`
- Searchable int `xml:"searchable,attr"`
+ ID string `xml:"id,attr"`
+ ParentID string `xml:"parentID,attr"`
+ Restricted int `xml:"restricted,attr"` // indicates whether the object is modifiable
+ Title string `xml:"dc:title"`
+ Class string `xml:"upnp:class"`
+ Icon string `xml:"upnp:icon,omitempty"`
+ Date Timestamp `xml:"dc:date"`
+ Artist string `xml:"upnp:artist,omitempty"`
+ Album string `xml:"upnp:album,omitempty"`
+ Genre string `xml:"upnp:genre,omitempty"`
+ AlbumArtURI string `xml:"upnp:albumArtURI,omitempty"`
+ Searchable int `xml:"searchable,attr"`
+ SearchXML string `xml:",innerxml"`
+}
+
+// Timestamp wraps time.Time for formatting purposes
+type Timestamp struct {
+ time.Time
+}
+
+// MarshalXML formats the Timestamp per DIDL-Lite spec
+func (t Timestamp) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ return e.EncodeElement(t.Format("2006-01-02"), start)
}