dlna: add some additional metadata, headers, and samsung extensions

Again, mostly just copying what I see in other implementations.  This
does seem to have done the trick so that I can now pause, fast forward,
rewind, etc., on my Samsung F series.
This commit is contained in:
Dan Walters 2019-05-26 13:45:01 -05:00 committed by Nick Craig-Wood
parent 78d38dda56
commit e5464a2a35
4 changed files with 107 additions and 17 deletions

View file

@ -192,6 +192,14 @@ func (cds *contentDirectoryService) Handle(action string, argsXML []byte, r *htt
"Result": didlLite(string(result)), "Result": didlLite(string(result)),
"UpdateID": cds.updateIDString(), "UpdateID": cds.updateIDString(),
}, nil }, nil
case "BrowseMetadata":
result, err := xml.Marshal(obj)
if err != nil {
return nil, err
}
return map[string]string{
"Result": didlLite(string(result)),
}, nil
default: default:
return nil, upnp.Errorf(upnp.ArgumentValueInvalidErrorCode, "unhandled browse flag: %v", browse.BrowseFlag) return nil, upnp.Errorf(upnp.ArgumentValueInvalidErrorCode, "unhandled browse flag: %v", browse.BrowseFlag)
} }
@ -199,6 +207,19 @@ func (cds *contentDirectoryService) Handle(action string, argsXML []byte, r *htt
return map[string]string{ return map[string]string{
"SearchCaps": "", "SearchCaps": "",
}, nil }, nil
// Samsung Extensions
case "X_GetFeatureList":
return map[string]string{
"FeatureList": `<Features xmlns="urn:schemas-upnp-org:av:avs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd">
<Feature name="samsung.com_BASICVIEW" version="1">
<container id="/" type="object.item.imageItem"/>
<container id="/" type="object.item.audioItem"/>
<container id="/" type="object.item.videoItem"/>
</Feature>
</Features>`}, nil
case "X_SetBookmark":
// just ignore
return map[string]string{}, nil
default: default:
return nil, upnp.InvalidActionError return nil, upnp.InvalidActionError
} }

File diff suppressed because one or more lines are too long

View file

@ -345,6 +345,41 @@
</argument> </argument>
</argumentList> </argumentList>
</action> </action>
<action>
<name>X_GetFeatureList</name>
<argumentList>
<argument>
<name>FeatureList</name>
<direction>out</direction>
<relatedStateVariable>A_ARG_TYPE_Featurelist</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>X_SetBookmark</name>
<argumentList>
<argument>
<name>CategoryType</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_CategoryType</relatedStateVariable>
</argument>
<argument>
<name>RID</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_RID</relatedStateVariable>
</argument>
<argument>
<name>ObjectID</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
</argument>
<argument>
<name>PosSecond</name>
<direction>in</direction>
<relatedStateVariable>A_ARG_TYPE_PosSec</relatedStateVariable>
</argument>
</argumentList>
</action>
</actionList> </actionList>
<serviceStateTable> <serviceStateTable>
<stateVariable sendEvents="no"> <stateVariable sendEvents="no">
@ -445,5 +480,25 @@
<name>A_ARG_TYPE_URI</name> <name>A_ARG_TYPE_URI</name>
<dataType>uri</dataType> <dataType>uri</dataType>
</stateVariable> </stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_CategoryType</name>
<dataType>ui4</dataType>
<defaultValue />
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_RID</name>
<dataType>ui4</dataType>
<defaultValue />
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_PosSec</name>
<dataType>ui4</dataType>
<defaultValue />
</stateVariable>
<stateVariable sendEvents="no">
<name>A_ARG_TYPE_Featurelist</name>
<dataType>string</dataType>
<defaultValue />
</stateVariable>
</serviceStateTable> </serviceStateTable>
</scpd> </scpd>

View file

@ -14,6 +14,7 @@ import (
"text/template" "text/template"
"time" "time"
dms_dlna "github.com/anacrolix/dms/dlna"
"github.com/anacrolix/dms/soap" "github.com/anacrolix/dms/soap"
"github.com/anacrolix/dms/ssdp" "github.com/anacrolix/dms/ssdp"
"github.com/anacrolix/dms/upnp" "github.com/anacrolix/dms/upnp"
@ -61,12 +62,10 @@ players might show files that they are not able to play back correctly.
} }
const ( const (
serverField = "Linux/3.4 DLNADOC/1.50 UPnP/1.0 DMS/1.0" serverField = "Linux/3.4 DLNADOC/1.50 UPnP/1.0 DMS/1.0"
rootDeviceType = "urn:schemas-upnp-org:device:MediaServer:1" rootDescPath = "/rootDesc.xml"
rootDeviceModelName = "rclone" resPath = "/res"
resPath = "/res" serviceControlURL = "/ctl"
rootDescPath = "/rootDesc.xml"
serviceControlURL = "/ctl"
) )
type server struct { type server struct {
@ -153,7 +152,9 @@ func (s *server) ModelNumber() string {
// //
// For rendering, it is passed the server object for context. // For rendering, it is passed the server object for context.
var rootDescTmpl = template.Must(template.New("rootDesc").Parse(`<?xml version="1.0"?> var rootDescTmpl = template.Must(template.New("rootDesc").Parse(`<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0"> <root xmlns="urn:schemas-upnp-org:device-1-0"
xmlns:dlna="urn:schemas-dlna-org:device-1-0"
xmlns:sec="http://www.sec.co.kr/dlna">
<specVersion> <specVersion>
<major>1</major> <major>1</major>
<minor>0</minor> <minor>0</minor>
@ -169,6 +170,11 @@ var rootDescTmpl = template.Must(template.New("rootDesc").Parse(`<?xml version="
<modelURL>https://rclone.org/</modelURL> <modelURL>https://rclone.org/</modelURL>
<serialNumber>00000000</serialNumber> <serialNumber>00000000</serialNumber>
<UDN>{{.RootDeviceUUID}}</UDN> <UDN>{{.RootDeviceUUID}}</UDN>
<dlna:X_DLNACAP/>
<dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>
<dlna:X_DLNADOC>M-DMS-1.50</dlna:X_DLNADOC>
<sec:ProductCap>smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec</sec:ProductCap>
<sec:X_ProductCap>smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec</sec:X_ProductCap>
<iconList> <iconList>
<icon> <icon>
<mimetype>image/png</mimetype> <mimetype>image/png</mimetype>
@ -184,7 +190,7 @@ var rootDescTmpl = template.Must(template.New("rootDesc").Parse(`<?xml version="
<depth>8</depth> <depth>8</depth>
<url>/static/rclone-120x120.png</url> <url>/static/rclone-120x120.png</url>
</icon> </icon>
</iconList> </iconList>
<serviceList> <serviceList>
<service> <service>
<serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType> <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
@ -206,7 +212,7 @@ var rootDescTmpl = template.Must(template.New("rootDesc").Parse(`<?xml version="
<SCPDURL>/static/X_MS_MediaReceiverRegistrar.xml</SCPDURL> <SCPDURL>/static/X_MS_MediaReceiverRegistrar.xml</SCPDURL>
<controlURL>/ctl</controlURL> <controlURL>/ctl</controlURL>
<eventSubURL></eventSubURL> <eventSubURL></eventSubURL>
</service> </service>
</serviceList> </serviceList>
<presentationURL>/</presentationURL> <presentationURL>/</presentationURL>
</device> </device>
@ -280,6 +286,14 @@ func (s *server) resourceHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10)) w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10))
// add some DLNA specific headers
if r.Header.Get("getContentFeatures.dlna.org") != "" {
w.Header().Set("contentFeatures.dlna.org", dms_dlna.ContentFeatures{
SupportRange: true,
}.String())
}
w.Header().Set("transferMode.dlna.org", "Streaming")
file := node.(*vfs.File) file := node.(*vfs.File)
in, err := file.Open(os.O_RDONLY) in, err := file.Open(os.O_RDONLY)
if err != nil { if err != nil {