forked from TrueCloudLab/rclone
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:
parent
78d38dda56
commit
e5464a2a35
4 changed files with 107 additions and 17 deletions
|
@ -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
|
@ -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>
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue