@@ -119,12 +119,15 @@
 playlist_fmt_prio = [
    "pls", "xspf", "asx", "smil", "jamj", "json", "m3u", "asf", "raw"
 ]
 
 # custom stream domain (with faux audioformat) handlers
+#  - may contain both "audio/x-service" handlers to convert playlist formsts
+#  - and "urn:service" resolvers (which fetch an #id/page to extract actual stram url)
 handler = {
-    # "audio/soundcloud": callback(),
+    # "audio/soundcloud": playlist_callback(),
+    # "urn:reciva": stream_resolve(),
 }
 
 
 
 # Exec wrapper
@@ -139,10 +142,12 @@
 
 # Invokes player/recorder for stream url and format
 def run_fmt_url(row={}, audioformat="audio/mpeg", source="pls", assoc={}, append=None):
     if audioformat in handler:
         handler[audioformat](row, audioformat, source, assoc)
+    elif row.get("url", "").startswith("urn:"):
+        row = resolve_urn(row);
     else:
         cmd = mime_app(audioformat, assoc)
         cmd = interpol(cmd, source, row)
         if append:
             cmd = re.sub('(["\']?\s*)$', " " + append + "\\1", cmd)
@@ -187,10 +192,22 @@
     for match in [ fmt, major + "/*", "*/*", "video/*", "audio/*" ]:
         if cmd_list.get(match):
             return cmd_list[match]
     log.ERR("No audio player for stream type found")
 
+
+# Is called upon rows containing an url starting with "urn:service:#id",
+# calls the handler from the channel plugin to look up the page and find
+# the actual streaming url
+def resolve_urn(row):
+    if row["url"].startswith("urn:"):
+        urn_service = ":".join(row["url"].split(":")[:2])
+        if urn_service in handler:
+            row = handler[urn_service](row)
+        else:
+            log.WARN("There's currently no action.handler[] for %s:#id streaming addresses (likely disabled channel plugin)." % urn_service)
+    return row
 
 
 # Replaces instances of %m3u, %pls, %srv in a command string
 # ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 #  · Also understands short aliases %l, %f, %d.