Project

General

Profile

Feature #4461 » 0001-hdhomerun-Add-HDHomeRun-server-support-for-LiveTV-on.patch

Em Smith, 2020-07-13 15:32

View differences:

configure
28 28
  "satip_server:yes"
29 29
  "satip_client:yes"
30 30
  "hdhomerun_client:no"
31
  "hdhomerun_server:yes"
31 32
  "hdhomerun_static:yes"
32 33
  "iptv:yes"
33 34
  "tsfile:yes"
docs/property/config_hdhomerun_server_username.md
1
Tvheadend can pretend to be an HDHomeRun device.
2
This is only available if Tvheadend has been compiled
3
with the feature enabled.
4

  
5
This is currently an experimental feature.  It may
6
be withdrawn in the future.
7

  
8
This allows some media players to be able to access
9
Live TV channels.  Some other media players use a
10
different technique to access HDHomeRun so will not
11
be able to play Live TV channels.
12

  
13
On the media player, autodetect of devices will not work.
14
Instead, you need to enter the IP address and port
15
number of the Tvheadend web interface.  For example
16
```1.2.3.4:9981```.  If the media player does not
17
support manually entering details then it is not
18
supported.
19

  
20
Typically the media player will then ask for xmltv
21
details to populate the TV Guide.  This can normally
22
be retrieved from Tvheadend via the web interface.
23
For example ```http://1.2.3.4:9981/xmltv/channels```.
src/config.c
2085 2085
PROP_DOC(config_channelname_scheme)
2086 2086
PROP_DOC(config_picon_path)
2087 2087
PROP_DOC(config_picon_servicetype)
2088
PROP_DOC(config_hdhomerun_server_username)
2088 2089
PROP_DOC(viewlevel_config)
2089 2090
PROP_DOC(themes)
2090 2091

  
......
2507 2508
      .opts   = PO_HIDDEN | PO_EXPERT,
2508 2509
      .group  = 6
2509 2510
    },
2511
    {
2512
      .type   = PT_STR,
2513
      .id     = "hdhomerun_server_username",
2514
      .name   = N_("Tvheadend username for HDHomeRun Server Emulation"),
2515
      .desc   = N_("When Tvheadend is acting as an HDHomeRun Server "
2516
                   "(emulating an HDHomeRun device for downstream "
2517
                   "media devices to stream Live TV) then "
2518
                   "we use this user for determining permissions "
2519
                   "such as channels that can be streamed.  "
2520
                   "This user must have basic streaming permissions.  "
2521
                   "This user must also have persistent "
2522
                   "authentication enabled in the user password settings.  "
2523
                   "It is strongly recommended that IP Blocking is used to "
2524
                   "prevent access from outside your network."
2525
                  ),
2526
      .doc    = prop_doc_config_hdhomerun_server_username,
2527
      .off    = offsetof(config_t, hdhomerun_server_username),
2528
      .opts   = PO_EXPERT
2529
#if !ENABLE_HDHOMERUN_SERVER
2530
      | PO_PHIDDEN
2531
#endif
2532
      ,
2533
      .group  = 6,
2534
    },
2535
    {
2536
      .type   = PT_U32,
2537
      .id     = "hdhomerun_server_tuner_count",
2538
      .name   = N_("Number of tuners to export for HDHomeRun Server Emulation"),
2539
      .desc   = N_("When Tvheadend is acting as an HDHomeRun Server "
2540
                   "(emulating an HDHomeRun device for downstream "
2541
                   "media devices to stream Live TV) then "
2542
                   "we tell clients that we have this number of tuners. "
2543
                   "This is necessary since some clients artificially limit "
2544
                   "connections based on tuner count, even though several "
2545
                   "channels may share a multiplex on one tuner. "
2546
                   "The HDHomeRun interface can not distinguish between "
2547
                   "different types of tuner in a mixed system with "
2548
                   "satellite, aerial and cable. "
2549
                   "The actual number or types of tuners used by Tvheadend is "
2550
                   "not affected by this value.  Tvheadend will "
2551
                   "allocate tuners automatically.  "
2552
                   "Set to zero for Tvheadend to use a default value."
2553
                  ),
2554
      .off    = offsetof(config_t, hdhomerun_server_tuner_count),
2555
      .opts   = PO_EXPERT
2556
#if !ENABLE_HDHOMERUN_SERVER
2557
      | PO_PHIDDEN
2558
#endif
2559
      ,
2560
      .group  = 6,
2561
    },
2562
    {
2563
      .type   = PT_STR,
2564
      .id     = "hdhomerun_server_model_name",
2565
      .name   = N_("Tvheadend model name for HDHomeRun Server Emulation"),
2566
      .desc   = N_("When Tvheadend is acting as an HDHomeRun Server "
2567
                   "(emulating an HDHomeRun device for downstream "
2568
                   "media devices to stream Live TV) then "
2569
                   "we use this as the type of HDHomeRun model number "
2570
                   "that we send to clients.  Some clients may require "
2571
                   "a specific model number to work.  Leave blank "
2572
                   "for Tvheadend to use a default."
2573
                  ),
2574
      .off    = offsetof(config_t, hdhomerun_server_model_name),
2575
      .opts   = PO_EXPERT
2576
#if !ENABLE_HDHOMERUN_SERVER
2577
      | PO_PHIDDEN
2578
#endif
2579
      ,
2580
      .group  = 6,
2581
    },
2582
    {
2583
      .type   = PT_BOOL,
2584
      .id     = "hdhomerun_server_enable",
2585
      .name   = N_("Enable HDHomeRun Server Emulation"),
2586
      .desc   = N_("Enable the Tvheadend server to emulate "
2587
                   "an HDHomeRun server.  This allows LiveTV "
2588
                   "to be used on some media servers."
2589
                  ),
2590
      .off    = offsetof(config_t, hdhomerun_server_enable),
2591
      .opts   = PO_EXPERT
2592
#if !ENABLE_HDHOMERUN_SERVER
2593
      | PO_PHIDDEN
2594
#endif
2595
,
2596
      .group  = 6
2597
    },
2510 2598
    {
2511 2599
      .type   = PT_STR,
2512 2600
      .id     = "http_user_agent",
src/config.h
74 74
  char *hdhomerun_ip;
75 75
  char *local_ip;
76 76
  int local_port;
77
  char *hdhomerun_server_username;
78
  uint32_t hdhomerun_server_tuner_count;
79
  char *hdhomerun_server_model_name;
80
  int hdhomerun_server_enable;
77 81
} config_t;
78 82

  
79 83
extern const idclass_t config_class;
src/htsmsg.c
1589 1589
  }
1590 1590
  return 0;
1591 1591
}
1592

  
1593

  
1594
// Based on htsbuf_vqprintf, but can't easily share code since we rely
1595
// on stack allocations.
1596
static void
1597
htsmsg_add_str_ap(htsmsg_t *msg, const char *name, const char *fmt, va_list ap0)
1598
{
1599
  // First try to format it on-stack
1600
  va_list ap;
1601
  int n;
1602
  size_t size;
1603
  char buf[100], *p, *np;
1604

  
1605
  va_copy(ap, ap0);
1606
  n = vsnprintf(buf, sizeof(buf), fmt, ap);
1607
  va_end(ap);
1608
  if(n > -1 && n < sizeof(buf)) {
1609
    htsmsg_add_str(msg, name, buf);
1610
    return;
1611
  }
1612

  
1613
  // Else, do allocations
1614
  size = sizeof(buf) * 2;
1615

  
1616
  p = malloc(size);
1617
  while (1) {
1618
    /* Try to print in the allocated space. */
1619
    va_copy(ap, ap0);
1620
    n = vsnprintf(p, size, fmt, ap);
1621
    va_end(ap);
1622
    if(n > -1 && n < size) {
1623
      htsmsg_add_str(msg, name, p);
1624
      // Copy taken by htsmsg_add_str.
1625
      free (p);
1626
      return;
1627
    }
1628
    /* Else try again with more space. */
1629
    if (n > -1)    /* glibc 2.1 */
1630
      size = n+1; /* precisely what is needed */
1631
    else           /* glibc 2.0 */
1632
      size *= 2;  /* twice the old size */
1633
    if ((np = realloc (p, size)) == NULL) {
1634
      free(p);
1635
      abort();
1636
    } else {
1637
      p = np;
1638
    }
1639
  }
1640
}
1641

  
1642
void
1643
htsmsg_add_str_printf(htsmsg_t *msg, const char *name, const char *fmt, ...)
1644
{
1645
  va_list ap;
1646
  va_start(ap, fmt);
1647
  htsmsg_add_str_ap(msg, name, fmt, ap);
1648
  va_end(ap);
1649
}
src/htsmsg.h
213 213
 */
214 214
void htsmsg_add_str_exclusive(htsmsg_t *msg, const char *str);
215 215

  
216
/**
217
 * Add a string using printf-style for the value.
218
 */
219
void
220
htsmsg_add_str_printf(htsmsg_t *msg, const char *name, const char *fmt, ...)
221
  __attribute__((format(printf,3,4)));;
222

  
216 223
/**
217 224
 * Add/update a string field
218 225
 */
src/htsmsg_json.h
29 29

  
30 30
void htsmsg_json_serialize(htsmsg_t *msg, htsbuf_queue_t *hq, int pretty);
31 31

  
32
__attribute__((warn_unused_result))
32 33
char *htsmsg_json_serialize_to_str(htsmsg_t *msg, int pretty);
33 34

  
34 35
struct rstr *htsmsg_json_serialize_to_rstr(htsmsg_t *msg, const char *prefix);
src/webui/webui.c
25 25
#include "http.h"
26 26
#include "tcp.h"
27 27
#include "webui.h"
28
#include "htsmsg_json.h"
28 29
#include "dvr/dvr.h"
29 30
#include "filebundle.h"
30 31
#include "streaming.h"
......
1597 1598
  return page_play_(hc, remain, opaque, URLAUTH_CODE);
1598 1599
}
1599 1600

  
1601

  
1602

  
1603
#if ENABLE_HDHOMERUN_SERVER
1604
/**
1605
 * Dummy password verify callback for HDHomeRun.  We need this to
1606
 * ensure we can get the aa_auth information for the user in to the
1607
 * access_t since it is not populated by an access_get_by_username.
1608
 * Since the user is configured solely on the server, the "passwd" is
1609
 * always valid.
1610
 *
1611
 * We have the username configured on the server (not the client)
1612
 * since HDHomeRun clients usually do not allow entry of credentials.
1613
 *
1614
 * This is called via the "access_get" call which does IP checking
1615
 * (configured via the Users/IP Blocking Records tab in expert
1616
 * setting).
1617
 */
1618
static int
1619
hdhomerun_server_verify_callback(void *aux, const char *passwd)
1620
{
1621
  return 1;
1622
}
1623

  
1624

  
1625
static const char *hdhomerun_get_server_name(void)
1626
{
1627
  return config.server_name ?: "Tvheadend";
1628
}
1629

  
1630
/**
1631
 * Our unique device id is calculated from our server's name
1632
 * in the general config tab.
1633
 */
1634
static uint32_t hdhomerun_get_deviceid(void)
1635
{
1636
  const char *server_name = hdhomerun_get_server_name();
1637
  const uint32_t deviceid = tvh_crc32((const uint8_t*)server_name, strlen(server_name), 0);
1638
  return deviceid;
1639
}
1640

  
1641
/// Get the model name, defaulting to a commonly used version.
1642
static const char *hdhomerun_get_model_name(void)
1643
{
1644
  if (config.hdhomerun_server_model_name && !strempty(config.hdhomerun_server_model_name))
1645
    return config.hdhomerun_server_model_name;
1646
  else
1647
    return "HDTC-2US";
1648
}
1649

  
1650

  
1651
/**
1652
 * @param fail_log_reason Log this reason if permissions fail.
1653
 * @return Permission for the verified user (caller owns the memory) or NULL.
1654
 */
1655
__attribute__((warn_unused_result))
1656
static access_t *hdhomerun_verify_user_permission(const http_connection_t *hc,
1657
                                                  const char *fail_log_reason)
1658
{
1659
  /* Not explicitly enabled?  Then all calls fail. */
1660
  if (!config.hdhomerun_server_enable) {
1661
    tvhwarn(LS_WEBUI, "hdhomerun server not enabled but received request [%s]",
1662
            fail_log_reason?:"");
1663
    return NULL;
1664
  }
1665

  
1666
  const char *hdhr_user = config.hdhomerun_server_username ?: "";
1667
  access_t *perm = access_get(hc->hc_peer, hdhr_user, hdhomerun_server_verify_callback, NULL);
1668

  
1669
  if (access_verify2(perm, ACCESS_STREAMING)) {
1670
    /* Failed */
1671
    tvhwarn(LS_WEBUI, "hdhomerun server received request but no streaming permission for user [%s] [%d] [%s]",
1672
            hdhr_user ?: "<none>",
1673
            perm? perm->aa_rights : 0,
1674
            fail_log_reason?:"");
1675
    access_destroy(perm);
1676
    return NULL;
1677
  } else {
1678
    return perm;
1679
  }
1680
}
1681

  
1682

  
1683
/**
1684
 * Return the discovery information for HDHomeRun to give clients
1685
 * details of how to access the lineup.
1686
 *
1687
 * Our HDHomeRun server implementation uses a server configured
1688
 * username to determine access rather than passing it through the
1689
 * request.  This is because HDHomeRun clients are usually configured
1690
 * with only an IP address and port number and do not offer any input
1691
 * for credentials.
1692
 */
1693
static int
1694
hdhomerun_server_discover(http_connection_t *hc, const char *remain, void *opaque)
1695
{
1696
  access_t *perm = hdhomerun_verify_user_permission(hc, "discover");
1697
  if (!perm)
1698
    return http_noaccess_code(hc);
1699

  
1700
  char http_ip[128];
1701
  htsbuf_queue_t *hq = &hc->hc_reply;
1702
  const char *server_name = hdhomerun_get_server_name();
1703
  const uint32_t deviceid = hdhomerun_get_deviceid();
1704

  
1705
  tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip));
1706

  
1707
  /* The contents below for the discovery message are based on tvhProxy */
1708
  htsmsg_t *msg = htsmsg_create_map();
1709
  htsmsg_add_str(msg, "FriendlyName", server_name);
1710
  htsmsg_add_str(msg,"FirmwareVersion", tvheadend_version);
1711
  // Currently hardcoded until we encounter a client that has a
1712
  // problem.
1713
  htsmsg_add_str(msg, "FirmwareName", "hdhomerun_atsc");
1714
  /* We use same value for model name/number to avoid too many user
1715
   * configuration options.
1716
   */
1717
  htsmsg_add_str(msg, "ModelNumber", hdhomerun_get_model_name());
1718
  htsmsg_add_str(msg, "Manufacturer", "Tvheadend");
1719
  // Random string, but has to be fixed length.
1720
  htsmsg_add_str(msg, "DeviceAuth", "3xw5UaJXhVShHEBoy76FuYQi");
1721
  htsmsg_add_str_printf(msg, "BaseURL", "http://%s:%u", http_ip, tvheadend_webui_port);
1722
  htsmsg_add_str_printf(msg, "DeviceID", "%08X", deviceid);
1723
  htsmsg_add_str_printf(msg, "LineupURL", "http://%s:%u/lineup.json", http_ip, tvheadend_webui_port);
1724

  
1725
  // If user has not explicitly set a count then we use a default.
1726
  // The actual number of tuners is unknown since we allow multiplex
1727
  // sharing and some channels may actually be iptv channels so not
1728
  // use a tuner at all.
1729
  htsmsg_add_u32(msg, "TunerCount", config.hdhomerun_server_tuner_count ?: 6);
1730

  
1731
  char *json = htsmsg_json_serialize_to_str(msg, 1);
1732
  htsbuf_append_str(hq, json);
1733
  free(json);
1734
  http_output_content(hc, "application/json");
1735
  access_destroy(perm);
1736
  return 0;
1737
}
1738

  
1739

  
1740
/**
1741
 * Return the channel lineup for HDHomeRun
1742
 */
1743
static int
1744
hdhomerun_server_lineup(http_connection_t *hc, const char *remain, void *opaque)
1745
{
1746
  access_t *perm = hdhomerun_verify_user_permission(hc, "lineup");
1747
  if (!perm)
1748
    return http_noaccess_code(hc);
1749

  
1750
  htsbuf_queue_t *hq = &hc->hc_reply;
1751
  channel_t *ch;
1752
  const char *name;
1753
  const char *blank;
1754
  const char *chnum_str;
1755
  const int use_auth = perm && perm->aa_auth && !strempty(perm->aa_auth);
1756
  char buf1[128], chnum[32], ubuf[UUID_HEX_SIZE];
1757
  char url[1024];
1758
  char http_ip[128];
1759
  /* We use the UI flags to determine if we should include channel
1760
   * numbers/sources in the name.  This can help distinguish channels
1761
   * when you have multiple different sources of the same channel such
1762
   * as satellite and aerial.
1763
   */
1764
  const int flags =
1765
    (config.chname_num ? CHANNEL_ENAME_NUMBERS : 0) |
1766
    (config.chname_src ? CHANNEL_ENAME_SOURCES : 0);
1767
  int is_first = 1;
1768

  
1769
  tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip));
1770
  blank = tvh_gettext_lang(perm->aa_lang_ui, channel_blank_name);
1771
  htsbuf_append_str(hq, "[");
1772
  tvh_mutex_lock(&global_lock);
1773
  CHANNEL_FOREACH(ch) {
1774
    if (!channel_access(ch, perm, 0) || !ch->ch_enabled)
1775
      continue;
1776
    if (!is_first)
1777
      htsbuf_append_str(hq, ", \n");
1778
    name = channel_get_ename(ch, buf1, sizeof(buf1), blank, flags);
1779
    htsbuf_append_str(hq, "{ \"GuideName\" : ");
1780
    htsbuf_append_and_escape_jsonstr(hq, name);
1781
    htsbuf_append_str(hq, ", \"GuideNumber\" : ");
1782
    /* channel_get_number_as_str returns NULL if no channel number! */
1783
    chnum_str = channel_get_number_as_str(ch, chnum, sizeof(chnum));
1784
    htsbuf_append_and_escape_jsonstr(hq, chnum_str ? chnum_str : "0");
1785
    htsbuf_append_str(hq, ", \"URL\" : ");
1786
    sprintf(url, "http://%s:%u/stream/channel/%s?profile=pass%s%s",
1787
            http_ip,
1788
            tvheadend_webui_port,
1789
            channel_get_uuid(ch, ubuf),
1790
            use_auth? "&auth=" : "",
1791
            use_auth ? perm->aa_auth : "");
1792
    htsbuf_append_and_escape_jsonstr(hq, url);
1793
    htsbuf_append_str(hq, "}");
1794
    is_first = 0;
1795
  }
1796
  tvh_mutex_unlock(&global_lock);
1797
  htsbuf_append_str(hq, "]");
1798
  http_output_content(hc, "application/json");
1799
  access_destroy(perm);
1800
  return 0;
1801
}
1802

  
1803

  
1804
static int
1805
hdhomerun_server_lineup_status(http_connection_t *hc, const char *remain, void *opaque)
1806
{
1807
  access_t *perm = hdhomerun_verify_user_permission(hc, "lineup_status");
1808
  if (!perm)
1809
    return http_noaccess_code(hc);
1810

  
1811
  htsbuf_queue_t *hq = &hc->hc_reply;
1812
  /* The contents below for the discovery message are based on the forum. */
1813
  htsbuf_append_str(hq, "{\"ScanInProgress\":0,\"ScanPossible\":0,\"Source\":\"Antenna\",\"SourceList\":[\"Antenna\"]}");
1814
  http_output_content(hc, "application/json");
1815
  access_destroy(perm);
1816
  return 0;
1817
}
1818

  
1819

  
1820

  
1821
/** Some media players ignore the "scan not possible" and do a post to
1822
 * this function with "?scan=start".
1823
 *
1824
 * We currently ignore this request and just return success.
1825
 * This is because Tvheadend has separate scanning and mapping stages.
1826
 */
1827
static int
1828
hdhomerun_server_lineup_post(http_connection_t *hc, const char *remain, void *opaque)
1829
{
1830
  access_t *perm = hdhomerun_verify_user_permission(hc, "lineup_status");
1831
  if (!perm)
1832
    return http_noaccess_code(hc);
1833

  
1834
  // We can't send empty contents since the caller thinks empty (size
1835
  // 0) is "unknown length" (for streaming data).  So, we'll return an
1836
  // empty json document.
1837
  htsbuf_append_str(&hc->hc_reply, "{}");
1838
  http_output_content(hc, "application/json");
1839
  access_destroy(perm);
1840
  return 0;
1841
}
1842

  
1843
/**
1844
 * Needed for some clients.  This contains much the same as discover,
1845
 * but in xml format.
1846
 */
1847
static int
1848
hdhomerun_server_device_xml(http_connection_t *hc, const char *remain, void *opaque)
1849
{
1850
  access_t *perm = hdhomerun_verify_user_permission(hc, "device.xml");
1851
  if (!perm)
1852
    return http_noaccess_code(hc);
1853

  
1854
  const char *server_name = hdhomerun_get_server_name();
1855
  const char *model_name = hdhomerun_get_model_name();
1856
  /* Need to escape strings in xml */
1857
  char server_name_escaped[128];
1858
  char model_name_escaped[128];
1859
  char http_ip[128];
1860
  htsbuf_queue_t *hq = &hc->hc_reply;
1861
  const uint32_t deviceid = hdhomerun_get_deviceid();
1862

  
1863
  html_escape(server_name_escaped, server_name, sizeof(server_name_escaped));
1864
  html_escape(model_name_escaped, model_name, sizeof(model_name_escaped));
1865

  
1866
  tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip));
1867
  htsbuf_qprintf(hq, "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
1868
                 "<specVersion>"
1869
                 "<major>1</major>"
1870
                 "<minor>0</minor>"
1871
                 "</specVersion>"
1872
                 "<URLBase>http://%s:%u</URLBase>"
1873
                 "<device>"
1874
                 "<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"
1875
                 "<friendlyName>%s</friendlyName>"
1876
                 "<manufacturer>Tvheadend</manufacturer>"
1877
                 "<modelName>%s</modelName>"
1878
                 "<modelNumber>%s</modelNumber>"
1879
                 "<serialNumber></serialNumber>"
1880
                 /* Version 5 UUID (random) with top part as server id*/
1881
                 "<UDN>uuid:%8.8x-745e-5d9a-8903-4a02327a7e09</UDN>"
1882
                 "</device>"
1883
                 "</root>",
1884
                 http_ip, tvheadend_webui_port,
1885
                 server_name_escaped,
1886
                 // We'll use the same for model name and number to
1887
                 // avoid too much user configuration.  Some clients
1888
                 // may use the model name to infer characteristics.
1889
                 model_name_escaped,
1890
                 model_name_escaped,
1891
                 deviceid);
1892

  
1893
  http_output_content(hc, "application/xml");
1894
  access_destroy(perm);
1895
  return 0;
1896
}
1897
#endif  /* ENABLE_HDHOMERUN_SERVER */
1898

  
1600 1899
/**
1601 1900
 *
1602 1901
 */
......
2134 2433
  http_path_add("/satip_server", NULL, satip_server_http_page, ACCESS_ANONYMOUS);
2135 2434
#endif
2136 2435

  
2436
#if ENABLE_HDHOMERUN_SERVER
2437
  /* These names are specified in https://info.hdhomerun.com/info/http_api */
2438
  http_path_add("/discover.json", NULL, hdhomerun_server_discover, ACCESS_ANONYMOUS);
2439
  http_path_add("/lineup.json", NULL, hdhomerun_server_lineup, ACCESS_ANONYMOUS);
2440
  /* These names are not specified in the documents but are required to make Plex work. */
2441
  http_path_add("/lineup_status.json", NULL, hdhomerun_server_lineup_status, ACCESS_ANONYMOUS);
2442
  http_path_add("/lineup.post", NULL, hdhomerun_server_lineup_post, ACCESS_ANONYMOUS);
2443
  http_path_add("/device.xml", NULL, hdhomerun_server_device_xml, ACCESS_ANONYMOUS);
2444
#endif
2445

  
2137 2446
  http_path_add_modify("/play", NULL, page_play, ACCESS_ANONYMOUS, page_play_path_modify5);
2138 2447
  http_path_add_modify("/play/ticket", NULL, page_play_ticket, ACCESS_ANONYMOUS, page_play_path_modify12);
2139 2448
  http_path_add_modify("/play/auth", NULL, page_play_auth, ACCESS_ANONYMOUS, page_play_path_modify10);
(19-19/19)