├── .github └── workflows │ └── build.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── entrypoint.sh ├── pom.xml └── src └── main ├── java └── com │ └── github │ └── segator │ └── proxylive │ ├── Application.java │ ├── ProxyLiveConstants.java │ ├── ProxyLiveUtils.java │ ├── config │ ├── AuthenticationConfiguration.java │ ├── BufferingConfiguration.java │ ├── ChannelsConfiguration.java │ ├── EPGConfiguration.java │ ├── FFMpegConfiguration.java │ ├── FFMpegProfile.java │ ├── GEOIPDatasource.java │ ├── GitSource.java │ ├── HLSConfiguration.java │ ├── HttpLiveSource.java │ ├── JWTBasicFilter.java │ ├── JwtConfiguration.java │ ├── LDAPAutentication.java │ ├── MPEGTSConfiguration.java │ ├── PlexAuthentication.java │ ├── ProxyLiveConfiguration.java │ ├── RemoteTranscoder.java │ └── WebSecurityConfiguration.java │ ├── controller │ ├── AuthController.java │ ├── FrontendController.java │ ├── StreamController.java │ ├── ViewController.java │ └── WSController.java │ ├── entity │ ├── ApiChannel.java │ ├── Channel.java │ ├── ChannelSource.java │ ├── ClientInfo.java │ ├── EPGProgram.java │ ├── GEOInfo.java │ ├── JSONClientInfo.java │ ├── JSONResponse.java │ ├── JSONTask.java │ └── LoginResult.java │ ├── helper │ ├── AuthorityRoles.java │ └── JwtHelper.java │ ├── processor │ ├── DirectHLSTranscoderStreamProcessor.java │ ├── DirectTranscoderStreamProcessor.java │ ├── HttpSoureStreamProcessor.java │ ├── IHLSStreamProcessor.java │ ├── ISourceStream.java │ ├── IStreamMultiplexerProcessor.java │ ├── IStreamProcessor.java │ ├── RemoteTranscodeStreamProcessor.java │ └── StreamProcessorFactory.java │ ├── profiler │ └── FFmpegProfilerService.java │ ├── service │ ├── AuthenticationService.java │ ├── AuthenticationServiceFactory.java │ ├── ChannelM3u8Service.java │ ├── ChannelService.java │ ├── ChannelServiceFactory.java │ ├── ChannelTVHeadendService.java │ ├── ChannelURLService.java │ ├── ConfigurationService.java │ ├── EPGService.java │ ├── GeoIPService.java │ ├── LDAPAuthenticationService.java │ ├── PlexAuthenticationService.java │ ├── PrometheusMetrics.java │ ├── TokenService.java │ └── WithoutAuthenticationService.java │ ├── stream │ ├── BroadcastCircularBufferedOutputStream.java │ ├── ClientBroadcastedInputStream.java │ ├── FFmpegInputStream.java │ ├── ProcessInputStream.java │ ├── UDPInputStream.java │ ├── VideoInputStream.java │ ├── WebInputStream.java │ └── WithoutBlockingInputStream.java │ └── tasks │ ├── DirectTranscodeTask.java │ ├── HLSDirectTask.java │ ├── HttpDownloaderTask.java │ ├── IMultiplexerStreamer.java │ ├── IStreamTask.java │ ├── ProcessorTasks.java │ ├── RemoteTranscodeTask.java │ ├── StreamProcessorsSession.java │ └── StreamTaskFactory.java └── resources ├── application.yml ├── banner.txt └── logback-spring.xml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created 2 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | pull_request: 9 | release: 10 | types: [created] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up JDK 14 23 | uses: actions/setup-java@v2 24 | with: 25 | java-version: '21' 26 | distribution: 'temurin' 27 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 28 | settings-path: ${{ github.workspace }} # location for the settings.xml file 29 | 30 | - name: Build with Maven 31 | run: mvn -B package --file pom.xml 32 | - name: Login to DockerHub 33 | uses: docker/login-action@v1 34 | with: 35 | username: ${{ secrets.DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.DOCKERHUB_TOKEN }} 37 | - name: Docker meta 38 | id: meta 39 | uses: docker/metadata-action@v3 40 | with: 41 | images: segator/proxylive 42 | flavor: | 43 | latest=auto 44 | tags: | 45 | type=schedule 46 | type=ref,event=branch 47 | type=ref,event=pr 48 | type=semver,pattern=v{{version}} 49 | type=semver,pattern=v{{major}}.{{minor}}.x 50 | type=semver,pattern=v{{major}}.x 51 | type=sha 52 | - name: Docker Build & Push 53 | uses: docker/build-push-action@v2 54 | with: 55 | push: ${{ github.event_name != 'pull_request' }} 56 | images: segator/proxylive 57 | tags: ${{ steps.meta.outputs.tags }} 58 | labels: ${{ steps.meta.outputs.labels }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .idea/ 3 | ProxyLive.iml 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.9.8-ibm-semeru-21-jammy as builder 2 | WORKDIR /opt 3 | #RUN apt update && apt-get install xz-utils -y 4 | #ADD https://download.docker.com/linux/static/stable/x86_64/docker-27.0.3.tgz /opt/docker.tgz 5 | #RUN tar zxvf /opt/docker.tgz 6 | #ADD https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz /opt/ffmpeg.tar.xz 7 | #RUN tar -xJf /opt/ffmpeg.tar.xz && \ 8 | # mv ffmpeg-* ffmpeg 9 | 10 | ADD https://github.com/AdoptOpenJDK/semeru22-binaries/releases/download/jdk-22.0.1%2B8_openj9-0.45.0/ibm-semeru-open-jdk_x64_linux_22.0.1_8_openj9-0.45.0.tar.gz java.tar.gz 11 | RUN tar xvzf java.tar.gz && mv jdk* jdk 12 | COPY pom.xml /app/pom.xml 13 | COPY src /app/src 14 | 15 | WORKDIR /app 16 | RUN mvn clean install 17 | 18 | FROM linuxserver/ffmpeg:latest 19 | MAINTAINER Isaac Aymerich 20 | 21 | 22 | 23 | 24 | COPY --from=builder /opt/jdk /usr/java 25 | RUN ln -s /usr/java/bin/java /usr/bin/java 26 | #COPY --from=builder /opt/docker/docker /usr/bin/docker 27 | #COPY --from=builder /opt/ffmpeg/ffmpeg /usr/bin/ffmpeg 28 | COPY --from=builder /app/target/ProxyLive.jar /app/proxyLive.jar 29 | COPY --from=builder /app/target/application.yml /app/application.yml 30 | 31 | 32 | ENV LANG en_US.UTF-8 33 | ENV LC_ALL en_US.UTF-8 34 | 35 | 36 | ENV DEBUG_MODE false 37 | ENV PROFILE = "" 38 | ENV JAVA_OPTS="-Xms256m -Xmx1024m" 39 | ENV JAVA_HOME /usr/java 40 | 41 | COPY entrypoint.sh /entrypoint.sh 42 | EXPOSE 8080 43 | 44 | WORKDIR /app 45 | 46 | ENTRYPOINT ["/entrypoint.sh"] 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 segator 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DEBUG_STRING="" 3 | PROFILE_STRING="" 4 | if $DEBUG_MODE ; then 5 | DEBUG_STRING="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=6006" 6 | fi 7 | 8 | if [ ! -z "$PROFILE" ] 9 | then 10 | PROFILE_STRING="-Dspring.profiles.active=$PROFILE" 11 | fi 12 | java $DEBUG_STRING $JAVA_OPTS $PROFILE_STRING -jar /app/proxyLive.jar $@ 13 | 14 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | org.segator 5 | ProxyLive 6 | 1.0-SNAPSHOT 7 | jar 8 | 9 | UTF-8 10 | 21 11 | 21 12 | 0.12.6 13 | 4.2.0 14 | 1.26.1 15 | 2.16.1 16 | 3.14.0 17 | 3.3.1 18 | 3.1.0 19 | 1.1.1 20 | 1.8.0 21 | 6.10.0.202406032230-r 22 | 5.3.1 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-parent 27 | 3.3.1 28 | 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-actuator 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-security 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-logging 47 | 48 | 49 | org.slf4j 50 | slf4j-api 51 | 52 | 53 | ch.qos.logback 54 | logback-classic 55 | 56 | 57 | io.jsonwebtoken 58 | jjwt-api 59 | ${jjwt-api.version} 60 | 61 | 62 | io.jsonwebtoken 63 | jjwt-impl 64 | ${jjwt-api.version} 65 | runtime 66 | 67 | 68 | io.jsonwebtoken 69 | jjwt-jackson 70 | ${jjwt-api.version} 71 | runtime 72 | 73 | 74 | 75 | 76 | io.micrometer 77 | micrometer-core 78 | 79 | 80 | 81 | io.micrometer 82 | micrometer-registry-prometheus 83 | 84 | 85 | com.maxmind.geoip2 86 | geoip2 87 | ${geoip2.version} 88 | 89 | 90 | 91 | org.apache.commons 92 | commons-compress 93 | ${commons-compress.version} 94 | 95 | 96 | org.apache.commons 97 | commons-exec 98 | 1.3 99 | 100 | 101 | commons-io 102 | commons-io 103 | ${commons-io.version} 104 | 105 | 106 | org.apache.commons 107 | commons-lang3 108 | ${commons-lang3.version} 109 | 110 | 111 | commons-cli 112 | commons-cli 113 | ${commons-cli.version} 114 | 115 | 116 | 117 | com.googlecode.json-simple 118 | json-simple 119 | ${json-simple.version} 120 | 121 | 122 | org.eclipse.jgit 123 | org.eclipse.jgit 124 | ${org.eclipse.jgit.version} 125 | 126 | 127 | 128 | org.apache.httpcomponents.client5 129 | httpclient5 130 | ${httpclient5.version} 131 | 132 | 133 | 134 | 135 | org.projectlombok 136 | lombok 137 | 138 | 139 | 140 | javax.servlet 141 | javax.servlet-api 142 | ${javax.servlet-api.version} 143 | provided 144 | 145 | 146 | 147 | 148 | ${project.name} 149 | 150 | 151 | org.springframework.boot 152 | spring-boot-maven-plugin 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-resources-plugin 157 | ${maven-resources-plugin.version} 158 | 159 | 160 | copy-resource-one 161 | install 162 | 163 | copy-resources 164 | 165 | 166 | ${basedir}/destination-folder 167 | 168 | 169 | ${project.basedir}/src/main/resources 170 | ${project.build.directory} 171 | 172 | application.yml 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive; 25 | 26 | import org.springframework.boot.SpringApplication; 27 | import org.springframework.boot.autoconfigure.SpringBootApplication; 28 | import org.springframework.scheduling.annotation.EnableScheduling; 29 | 30 | /** 31 | * 32 | * @author Isaac Aymerich 33 | */ 34 | @SpringBootApplication 35 | @EnableScheduling 36 | public class Application { 37 | public static void main(String[] args) { 38 | SpringApplication.run(Application.class, args); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/ProxyLiveConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive; 25 | 26 | /** 27 | * 28 | * @author Isaac Aymerich 29 | */ 30 | public class ProxyLiveConstants { 31 | 32 | public static final int STREAM_MODE = 0; 33 | public static final int HLS_MODE = 1; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/ProxyLiveUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive; 25 | 26 | import jakarta.servlet.http.HttpServletRequest; 27 | import org.apache.commons.lang3.time.DurationFormatUtils; 28 | 29 | import java.util.ArrayList; 30 | import java.util.StringTokenizer; 31 | import java.util.regex.Matcher; 32 | import java.util.regex.Pattern; 33 | 34 | /** 35 | * 36 | * @author Isaac Aymerich 37 | */ 38 | public class ProxyLiveUtils { 39 | 40 | private static Pattern pattern = Pattern.compile("^(tvh|hls|dash)(s)?:\\/\\/(.+)$"); 41 | public static String getOS() { 42 | 43 | String OS = System.getProperty("os.name").toLowerCase(); 44 | 45 | if (OS.contains("win")) { 46 | return "win"; 47 | } else if (OS.contains("mac")) { 48 | return "mac"; 49 | } else if (OS.contains("nix") || OS.contains("nux") || OS.indexOf("aix") > 0) { 50 | return "unix"; 51 | } 52 | return null; 53 | } 54 | 55 | public static String getServerContext(HttpServletRequest request) { 56 | return request.getScheme() + "://" + request.getHeader("host") + request.getContextPath(); 57 | } 58 | 59 | public static String convertMilisToTime(long time) { 60 | return DurationFormatUtils.formatDurationWords(time, true, true); 61 | } 62 | 63 | public static String getRequestIP(HttpServletRequest request) { 64 | String ipAddress = request.getHeader("X-FORWARDED-FOR"); 65 | if (ipAddress == null) { 66 | ipAddress = request.getRemoteAddr(); 67 | } 68 | return ipAddress; 69 | } 70 | 71 | public static String getBrowserInfo(HttpServletRequest request) { 72 | return request.getHeader("User-Agent"); 73 | } 74 | 75 | public static String getBaseURL(HttpServletRequest req) { 76 | String forwardProto = req.getHeader("x-forwarded-proto"); 77 | String scheme = req.getScheme(); // http 78 | if(forwardProto!=null){ 79 | scheme=forwardProto; 80 | } 81 | 82 | String serverName = req.getServerName(); // hostname.com 83 | int serverPort = req.getServerPort(); // 80 84 | String contextPath = req.getContextPath(); // /mywebapp 85 | String servletPath = req.getServletPath(); // /servlet/MyServlet 86 | String pathInfo = req.getPathInfo(); // /a/b;c=123 87 | 88 | // Reconstruct original requesting URL 89 | StringBuilder url = new StringBuilder(); 90 | url.append(scheme).append("://").append(serverName); 91 | 92 | if (serverPort != 80 && serverPort != 443) { 93 | url.append(":").append(serverPort); 94 | } 95 | url.append(contextPath); 96 | 97 | return url.toString(); 98 | 99 | } 100 | public static String getURL(HttpServletRequest req) { 101 | return getURL(req,true); 102 | } 103 | public static String getURL(HttpServletRequest req,boolean withParameters) { 104 | String servletPath = req.getServletPath(); // /servlet/MyServlet 105 | String pathInfo = req.getPathInfo(); // /a/b;c=123 106 | String queryString = req.getQueryString(); // d=789 107 | 108 | 109 | StringBuilder url=new StringBuilder(getBaseURL(req)); 110 | url.append(servletPath); 111 | 112 | if (pathInfo != null) { 113 | url.append(pathInfo); 114 | } 115 | if (queryString != null && withParameters) { 116 | url.append("?").append(queryString); 117 | } 118 | return url.toString(); 119 | } 120 | 121 | public static String replaceSchemes(String url){ 122 | Matcher matcher = pattern.matcher(url); 123 | if(matcher.matches()){ 124 | return "http" + (matcher.group(2)!=null?matcher.group(2):"") + "://" +matcher.group(3); 125 | }else{ 126 | return url; 127 | } 128 | } 129 | 130 | public static String[] translateCommandline(String toProcess) { 131 | if (toProcess == null || toProcess.length() == 0) { 132 | //no command? no string 133 | return new String[0]; 134 | } 135 | // parse with a simple finite state machine 136 | 137 | final int normal = 0; 138 | final int inQuote = 1; 139 | final int inDoubleQuote = 2; 140 | int state = normal; 141 | final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true); 142 | final ArrayList result = new ArrayList(); 143 | final StringBuilder current = new StringBuilder(); 144 | boolean lastTokenHasBeenQuoted = false; 145 | 146 | while (tok.hasMoreTokens()) { 147 | String nextTok = tok.nextToken(); 148 | switch (state) { 149 | case inQuote: 150 | if ("\'".equals(nextTok)) { 151 | lastTokenHasBeenQuoted = true; 152 | state = normal; 153 | } else { 154 | current.append(nextTok); 155 | } 156 | break; 157 | case inDoubleQuote: 158 | if ("\"".equals(nextTok)) { 159 | lastTokenHasBeenQuoted = true; 160 | state = normal; 161 | } else { 162 | current.append(nextTok); 163 | } 164 | break; 165 | default: 166 | if ("\'".equals(nextTok)) { 167 | state = inQuote; 168 | } else if ("\"".equals(nextTok)) { 169 | state = inDoubleQuote; 170 | } else if (" ".equals(nextTok)) { 171 | if (lastTokenHasBeenQuoted || current.length() != 0) { 172 | result.add(current.toString()); 173 | current.setLength(0); 174 | } 175 | } else { 176 | current.append(nextTok); 177 | } 178 | lastTokenHasBeenQuoted = false; 179 | break; 180 | } 181 | } 182 | if (lastTokenHasBeenQuoted || current.length() != 0) { 183 | result.add(current.toString()); 184 | } 185 | if (state == inQuote || state == inDoubleQuote) { 186 | throw new RuntimeException("unbalanced quotes in " + toProcess); 187 | } 188 | return result.toArray(new String[result.size()]); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/AuthenticationConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.config; 7 | 8 | /** 9 | * 10 | * @author isaac 11 | */ 12 | public class AuthenticationConfiguration { 13 | 14 | private PlexAuthentication plex; 15 | private LDAPAutentication ldap; 16 | 17 | public PlexAuthentication getPlex() { 18 | return plex; 19 | } 20 | 21 | public void setPlex(PlexAuthentication plex) { 22 | this.plex = plex; 23 | } 24 | 25 | public LDAPAutentication getLdap() { 26 | return ldap; 27 | } 28 | 29 | public void setLdap(LDAPAutentication ldap) { 30 | this.ldap = ldap; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/BufferingConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.config; 25 | 26 | /** 27 | * 28 | * @author Isaac Aymerich 29 | */ 30 | public class BufferingConfiguration { 31 | 32 | private Integer chunkSize; 33 | private Integer broadcastBufferSize; 34 | 35 | public Integer getChunkSize() { 36 | return chunkSize; 37 | } 38 | 39 | public void setChunkSize(Integer chunkSize) { 40 | this.chunkSize = chunkSize; 41 | } 42 | 43 | public Integer getBroadcastBufferSize() { 44 | return broadcastBufferSize; 45 | } 46 | 47 | public void setBroadcastBufferSize(Integer broadcastBufferSize) { 48 | this.broadcastBufferSize = broadcastBufferSize; 49 | } 50 | 51 | 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/ChannelsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | import lombok.Data; 4 | import lombok.Value; 5 | 6 | @Data 7 | public class ChannelsConfiguration { 8 | GitSource git; 9 | String url; 10 | long refresh; 11 | String type; 12 | 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/EPGConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | public class EPGConfiguration { 4 | private String url; 5 | private long refresh; 6 | 7 | public String getUrl() { 8 | return url; 9 | } 10 | 11 | public void setUrl(String url) { 12 | this.url = url; 13 | } 14 | 15 | public long getRefresh() { 16 | return refresh; 17 | } 18 | 19 | public void setRefresh(long refresh) { 20 | this.refresh = refresh; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/FFMpegConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.config; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * 30 | * @author Isaac Aymerich 31 | */ 32 | public class FFMpegConfiguration { 33 | private List profiles; 34 | private String path; 35 | private HLSConfiguration hls; 36 | private MPEGTSConfiguration mpegTS; 37 | 38 | public HLSConfiguration getHls() { 39 | return hls; 40 | } 41 | 42 | public void setHls(HLSConfiguration hls) { 43 | this.hls = hls; 44 | } 45 | 46 | public String getPath() { 47 | return path; 48 | } 49 | 50 | public void setPath(String path) { 51 | this.path = path; 52 | } 53 | 54 | 55 | 56 | public List getProfiles() { 57 | return profiles; 58 | } 59 | 60 | public void setProfiles(List profiles) { 61 | this.profiles = profiles; 62 | } 63 | 64 | public MPEGTSConfiguration getMpegTS() { 65 | return mpegTS; 66 | } 67 | 68 | public void setMpegTS(MPEGTSConfiguration mpegTS) { 69 | this.mpegTS = mpegTS; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/FFMpegProfile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.config; 25 | 26 | import java.rmi.Remote; 27 | 28 | /** 29 | * 30 | * @author Isaac Aymerich 31 | */ 32 | public class FFMpegProfile { 33 | private String alias; 34 | private RemoteTranscoder transcoder; 35 | private String parameters; 36 | private Integer adaptiveBandWith; 37 | private String adaptiveResolution; 38 | 39 | public Integer getAdaptiveBandWith() { 40 | return adaptiveBandWith; 41 | } 42 | 43 | public void setAdaptiveBandWith(Integer adaptiveBandWith) { 44 | this.adaptiveBandWith = adaptiveBandWith; 45 | } 46 | 47 | public String getAdaptiveResolution() { 48 | return adaptiveResolution; 49 | } 50 | 51 | public void setAdaptiveResolution(String adaptiveResolution) { 52 | this.adaptiveResolution = adaptiveResolution; 53 | } 54 | 55 | public String getAlias() { 56 | return alias; 57 | } 58 | 59 | public void setAlias(String alias) { 60 | this.alias = alias; 61 | } 62 | 63 | public String getParameters() { 64 | return parameters; 65 | } 66 | 67 | public void setParameters(String parameters) { 68 | this.parameters = parameters; 69 | } 70 | 71 | public boolean isLocalTranscoding(){ 72 | return transcoder==null; 73 | } 74 | 75 | public RemoteTranscoder getTranscoder() { 76 | return transcoder; 77 | } 78 | 79 | public void setTranscoder(RemoteTranscoder transcoder) { 80 | this.transcoder = transcoder; 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/GEOIPDatasource.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | public class GEOIPDatasource { 4 | private String url; 5 | private boolean enabled; 6 | 7 | public String getUrl() { 8 | return url; 9 | } 10 | 11 | public void setUrl(String url) { 12 | this.url = url; 13 | } 14 | 15 | public boolean isEnabled() { 16 | return enabled; 17 | } 18 | 19 | public void setEnabled(boolean enable) { 20 | this.enabled = enable; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/GitSource.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | public class GitSource { 4 | private String repository; 5 | private String user; 6 | private String pass; 7 | private String branch="master"; 8 | 9 | public String getRepository() { 10 | return repository; 11 | } 12 | 13 | public void setRepository(String repository) { 14 | this.repository = repository; 15 | } 16 | 17 | public String getUser() { 18 | return user; 19 | } 20 | 21 | public void setUser(String user) { 22 | this.user = user; 23 | } 24 | 25 | public String getPass() { 26 | return pass; 27 | } 28 | 29 | public void setPass(String pass) { 30 | this.pass = pass; 31 | } 32 | 33 | public String getBranch() { 34 | return branch; 35 | } 36 | 37 | public void setBranch(String branch) { 38 | this.branch = branch; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/HLSConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.config; 25 | 26 | /** 27 | * 28 | * @author Isaac Aymerich 29 | */ 30 | public class HLSConfiguration { 31 | private Boolean enabled= false; 32 | private String tempPath; 33 | private String parameters; 34 | private String directParameters; 35 | private Integer timeout; 36 | 37 | public String getTempPath() { 38 | return tempPath; 39 | } 40 | 41 | public void setTempPath(String tempPath) { 42 | this.tempPath = tempPath; 43 | } 44 | 45 | public String getParameters() { 46 | return parameters; 47 | } 48 | 49 | public void setParameters(String parameters) { 50 | this.parameters = parameters; 51 | } 52 | 53 | public Integer getTimeout() { 54 | return timeout; 55 | } 56 | 57 | public void setTimeout(Integer timeout) { 58 | this.timeout = timeout; 59 | } 60 | 61 | public String getDirectParameters() { 62 | return directParameters; 63 | } 64 | 65 | public void setDirectParameters(String directParameters) { 66 | this.directParameters = directParameters; 67 | } 68 | 69 | public Boolean getEnabled() { 70 | return enabled; 71 | } 72 | 73 | public void setEnabled(Boolean enabled) { 74 | this.enabled = enabled; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/HttpLiveSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.config; 25 | 26 | /** 27 | * 28 | * @author Isaac Aymerich 29 | */ 30 | public class HttpLiveSource { 31 | private String tvheadendURL; 32 | private String m3u8URL; 33 | private EPGConfiguration epg; 34 | private int reconnectTimeout; 35 | private int channelListCacheTime=600; 36 | private ChannelsConfiguration channels; 37 | 38 | public String getTvheadendURL() { 39 | return tvheadendURL; 40 | } 41 | 42 | public void setTvheadendURL(String tvheadendURL) { 43 | this.tvheadendURL = tvheadendURL; 44 | } 45 | 46 | public EPGConfiguration getEpg() { 47 | return epg; 48 | } 49 | 50 | public void setEpg(EPGConfiguration epg) { 51 | this.epg = epg; 52 | } 53 | 54 | public ChannelsConfiguration getChannels() { 55 | return channels; 56 | } 57 | 58 | public void setChannels(ChannelsConfiguration channels) { 59 | this.channels = channels; 60 | } 61 | 62 | public int getReconnectTimeout() { 63 | return reconnectTimeout; 64 | } 65 | 66 | public void setReconnectTimeout(int reconnectTimeout) { 67 | this.reconnectTimeout = reconnectTimeout; 68 | } 69 | 70 | public int getChannelListCacheTime() { 71 | return channelListCacheTime; 72 | } 73 | 74 | public void setChannelListCacheTime(int channelListCacheTime) { 75 | this.channelListCacheTime = channelListCacheTime; 76 | } 77 | 78 | public String getM3u8URL() { 79 | return m3u8URL; 80 | } 81 | 82 | public void setM3u8URL(String m3u8URL) { 83 | this.m3u8URL = m3u8URL; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/JWTBasicFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | import com.github.segator.proxylive.helper.JwtHelper; 4 | import com.github.segator.proxylive.service.AuthenticationService; 5 | import io.jsonwebtoken.*; 6 | 7 | import org.apache.hc.core5.net.URIBuilder; 8 | import org.eclipse.jgit.util.Base64; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.web.filter.OncePerRequestFilter; 14 | 15 | import javax.crypto.SecretKey; 16 | import javax.crypto.spec.SecretKeySpec; 17 | import jakarta.servlet.http.HttpServletResponse; 18 | import java.io.IOException; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.stream.Collectors; 23 | 24 | @Component 25 | public class JWTBasicFilter extends OncePerRequestFilter { 26 | 27 | private final String HEADER = "Authorization"; 28 | private final String PREFIXBearer = "Bearer "; 29 | private final String PREFIXBasic = "Basic "; 30 | private final String PARAM_OLD_USERNAME="user"; 31 | private final String PARAM_OLD_PASSWORD="pass"; 32 | private final JwtConfiguration jwtConfiguration; 33 | private final AuthenticationService authenticationService; 34 | private final JwtHelper jwtHelper; 35 | 36 | public JWTBasicFilter(JwtConfiguration jwtConfiguration, AuthenticationService authenticationService, JwtHelper jwtHelper) { 37 | this.jwtConfiguration = jwtConfiguration; 38 | this.authenticationService = authenticationService; 39 | this.jwtHelper = jwtHelper; 40 | } 41 | 42 | private Claims validateToken(String jwtToken) { 43 | SecretKey secretKey = new SecretKeySpec(jwtConfiguration.getSecret().getBytes(),"HmacSHA512"); 44 | return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(jwtToken).getPayload(); 45 | } 46 | 47 | /** 48 | * Metodo para autenticarnos dentro del flujo de Spring 49 | * 50 | * @param claims 51 | */ 52 | private void setUpSpringAuthentication(Claims claims,String jwtToken) { 53 | @SuppressWarnings("unchecked") 54 | List authorities = (List) claims.get("authorities"); 55 | 56 | UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(claims.getSubject(), jwtToken, 57 | authorities.stream().map(role->new SimpleGrantedAuthority("ROLE_"+role)).collect(Collectors.toList())); 58 | SecurityContextHolder.getContext().setAuthentication(auth); 59 | 60 | } 61 | 62 | private String getJWTToken(jakarta.servlet.http.HttpServletRequest request) { 63 | String parameterToken = request.getParameter("token"); 64 | if(parameterToken!=null){ 65 | return parameterToken; 66 | } 67 | 68 | String authenticationHeader = request.getHeader(HEADER); 69 | if (authenticationHeader == null) { 70 | return null; 71 | } 72 | 73 | if(authenticationHeader.startsWith(PREFIXBearer)){ 74 | return authenticationHeader.replace(PREFIXBearer,""); 75 | }else if(authenticationHeader.startsWith(PREFIXBasic)){ 76 | String basicB64=authenticationHeader.replace(PREFIXBasic,""); 77 | String basicSecret = new String(Base64.decode(basicB64)); 78 | return basicSecret.split(":")[1]; 79 | } 80 | return null; 81 | } 82 | 83 | @Override 84 | protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException { 85 | try { 86 | if(!request.getRequestURI().startsWith("/login")) { 87 | String jwtToken = getJWTToken(request); 88 | if (jwtToken != null) { 89 | 90 | Claims claims = validateToken(jwtToken); 91 | if (claims.get("authorities") != null) { 92 | setUpSpringAuthentication(claims, jwtToken); 93 | } else { 94 | SecurityContextHolder.clearContext(); 95 | } 96 | } else { 97 | //Old Authentication system compatibility, redirecting user and pass to token on the fly with 301 redirect. 98 | String username = request.getParameter(PARAM_OLD_USERNAME); 99 | if (username == null) { 100 | username = "anonymous"; 101 | } 102 | String password = request.getParameter(PARAM_OLD_PASSWORD); 103 | if (authenticationService.loginUser(username, password)) { 104 | jwtToken = jwtHelper.createJwtForClaims(username, authenticationService.getUserRoles(username)); 105 | Map parameters = new HashMap<>(request.getParameterMap()); 106 | parameters.remove(PARAM_OLD_USERNAME); 107 | parameters.remove(PARAM_OLD_PASSWORD); 108 | parameters.put("token", new String[]{jwtToken}); 109 | response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT); 110 | URIBuilder uriBuilder = new URIBuilder(request.getRequestURI()); 111 | for (Map.Entry kv : parameters.entrySet()) { 112 | for (String value : kv.getValue()) { 113 | uriBuilder.addParameter(kv.getKey(), value); 114 | } 115 | } 116 | response.setHeader("Location", uriBuilder.toString()); 117 | return; 118 | } 119 | SecurityContextHolder.clearContext(); 120 | } 121 | 122 | filterChain.doFilter(request,response); 123 | } 124 | } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) { 125 | response.setStatus(HttpServletResponse.SC_FORBIDDEN); 126 | ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); 127 | }catch (jakarta.servlet.ServletException e){ 128 | throw e; 129 | }catch (IOException e) { 130 | throw new jakarta.servlet.ServletException(e); 131 | } catch (Exception e) { 132 | response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 133 | ((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/JwtConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class JwtConfiguration { 11 | private final Logger logger = LoggerFactory.getLogger(JwtConfiguration.class); 12 | private String secret; 13 | private Integer expireInHours; 14 | 15 | JwtConfiguration( @Value("${authentication.secret:}")String secret,@Value("${authentication.expireInHours}")Integer expireInHours){ 16 | if(secret.isEmpty()){ 17 | this.secret = RandomStringUtils.randomAlphanumeric(65); 18 | }else{ 19 | this.secret=secret; 20 | } 21 | this.expireInHours=expireInHours; 22 | } 23 | 24 | 25 | public String getSecret() { 26 | return secret; 27 | } 28 | 29 | public Integer getExpireInHours() { 30 | return expireInHours; 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/LDAPAutentication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.config; 7 | 8 | /** 9 | * 10 | * @author isaac 11 | */ 12 | public class LDAPAutentication { 13 | 14 | private String server, searchBase, user, password; 15 | 16 | public String getServer() { 17 | return server; 18 | } 19 | 20 | public void setServer(String server) { 21 | this.server = server; 22 | } 23 | 24 | public String getSearchBase() { 25 | return searchBase; 26 | } 27 | 28 | public void setSearchBase(String searchBase) { 29 | this.searchBase = searchBase; 30 | } 31 | 32 | public String getUser() { 33 | return user; 34 | } 35 | 36 | public void setUser(String user) { 37 | this.user = user; 38 | } 39 | 40 | public String getPassword() { 41 | return password; 42 | } 43 | 44 | public void setPassword(String password) { 45 | this.password = password; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/MPEGTSConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | public class MPEGTSConfiguration { 4 | private String parameters; 5 | 6 | public String getParameters() { 7 | return parameters; 8 | } 9 | 10 | public void setParameters(String parameters) { 11 | this.parameters = parameters; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/PlexAuthentication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.config; 7 | 8 | 9 | /** 10 | * 11 | * @author isaac 12 | */ 13 | public class PlexAuthentication { 14 | 15 | private String adminUser, adminPass, serverName; 16 | private long refresh=10800; 17 | 18 | public String getAdminUser() { 19 | return adminUser; 20 | } 21 | 22 | public void setAdminUser(String adminUser) { 23 | this.adminUser = adminUser; 24 | } 25 | 26 | public String getAdminPass() { 27 | return adminPass; 28 | } 29 | 30 | public void setAdminPass(String adminPass) { 31 | this.adminPass = adminPass; 32 | } 33 | 34 | public String getServerName() { 35 | return serverName; 36 | } 37 | 38 | public void setServerName(String serverName) { 39 | this.serverName = serverName; 40 | } 41 | 42 | public long getRefresh() { 43 | return refresh; 44 | } 45 | 46 | public void setRefresh(long refresh) { 47 | this.refresh = refresh; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/ProxyLiveConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.config; 25 | 26 | import com.github.segator.proxylive.helper.JwtHelper; 27 | import com.github.segator.proxylive.tasks.DirectTranscodeTask; 28 | import jakarta.annotation.PostConstruct; 29 | import lombok.Getter; 30 | import lombok.Setter; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | import org.springframework.boot.context.properties.ConfigurationProperties; 34 | 35 | 36 | import org.springframework.context.annotation.Configuration; 37 | 38 | 39 | 40 | /** 41 | * 42 | * @author Isaac Aymerich 43 | */ 44 | @Configuration 45 | @ConfigurationProperties 46 | public class ProxyLiveConfiguration { 47 | private final Logger logger = LoggerFactory.getLogger(DirectTranscodeTask.class); 48 | private final JwtHelper jwtHelper; 49 | @Setter 50 | @Getter 51 | private BufferingConfiguration buffers; 52 | @Getter 53 | @Setter 54 | private FFMpegConfiguration ffmpeg; 55 | @Getter 56 | @Setter 57 | private HttpLiveSource source; 58 | @Getter 59 | @Setter 60 | private GEOIPDatasource geoIP; 61 | @Setter 62 | @Getter 63 | private AuthenticationConfiguration authentication; 64 | @Getter 65 | @Setter 66 | private String userAgent; 67 | @Setter 68 | @Getter 69 | private int streamTimeout; 70 | 71 | public ProxyLiveConfiguration(JwtHelper jwtHelper) { 72 | this.jwtHelper = jwtHelper; 73 | } 74 | 75 | @PostConstruct 76 | public void initializeBean() { 77 | //If tvheadend input is set complete configuration 78 | if(source.getTvheadendURL()!=null){ 79 | if( source.getEpg().getUrl()==null) { 80 | source.getEpg().setUrl(source.getTvheadendURL() + "/xmltv/channels"); 81 | } 82 | if(source.getChannels().getUrl()==null) { 83 | source.getChannels().setUrl(source.getTvheadendURL()); 84 | } 85 | } 86 | } 87 | public int getStreamTimeoutMilis() { 88 | return streamTimeout*1000; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/RemoteTranscoder.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | public class RemoteTranscoder { 4 | private String endpoint; 5 | private String profile; 6 | 7 | public static RemoteTranscoder CreateFrom(RemoteTranscoder remoteTranscoder) { 8 | RemoteTranscoder rt= new RemoteTranscoder(); 9 | rt.setEndpoint(remoteTranscoder.getEndpoint()); 10 | rt.setProfile(remoteTranscoder.getProfile()); 11 | return rt; 12 | } 13 | public String getEndpoint() { 14 | return endpoint; 15 | } 16 | 17 | public void setEndpoint(String endpoint) { 18 | this.endpoint = endpoint; 19 | } 20 | 21 | public String getProfile() { 22 | return profile; 23 | } 24 | 25 | public void setProfile(String profile) { 26 | this.profile = profile; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "RemoteTranscoder{" + 32 | "endpoint='" + endpoint + '\'' + 33 | ", profile='" + profile + '\'' + 34 | '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/config/WebSecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.config; 2 | 3 | import com.github.segator.proxylive.helper.AuthorityRoles; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.config.Customizer; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 9 | import org.springframework.security.config.http.SessionCreationPolicy; 10 | import org.springframework.security.web.SecurityFilterChain; 11 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 12 | 13 | @Configuration 14 | public class WebSecurityConfiguration { 15 | 16 | private final JWTBasicFilter jwtBasicFilter; 17 | 18 | public WebSecurityConfiguration(JWTBasicFilter jwtBasicFilter) { 19 | this.jwtBasicFilter = jwtBasicFilter; 20 | } 21 | 22 | @Bean 23 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 24 | return http 25 | .cors(AbstractHttpConfigurer::disable) 26 | .csrf(AbstractHttpConfigurer::disable) 27 | .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 28 | .authorizeHttpRequests(configurer -> 29 | configurer 30 | .requestMatchers("/login", "/error", "/actuator/**").permitAll() 31 | .requestMatchers("/channel/**","/view/raw","/api/**").hasRole(AuthorityRoles.USER.getAuthority()) 32 | .requestMatchers("/view/**").hasRole(AuthorityRoles.ALLOW_ENCODING.getAuthority()) 33 | .requestMatchers("/ws/**").hasRole(AuthorityRoles.ADMIN.getAuthority()) 34 | .anyRequest().authenticated() 35 | ) 36 | .addFilterAfter(jwtBasicFilter, UsernamePasswordAuthenticationFilter.class) 37 | .build(); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.controller; 2 | 3 | import com.github.segator.proxylive.entity.LoginResult; 4 | import com.github.segator.proxylive.helper.JwtHelper; 5 | import com.github.segator.proxylive.service.AuthenticationService; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import org.springframework.web.server.ResponseStatusException; 12 | 13 | @RestController 14 | public class AuthController { 15 | 16 | private final JwtHelper jwtHelper; 17 | private final AuthenticationService authenticationService; 18 | 19 | public AuthController(JwtHelper jwtHelper, AuthenticationService authenticationService) { 20 | this.jwtHelper = jwtHelper; 21 | this.authenticationService = authenticationService; 22 | } 23 | 24 | @PostMapping(path = "/login", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE }) 25 | public LoginResult login( 26 | @RequestParam String username, 27 | @RequestParam String password) throws Exception { 28 | 29 | if (authenticationService.loginUser(username,password)) { 30 | String jwt = jwtHelper.createJwtForClaims(username, authenticationService.getUserRoles(username)); 31 | return new LoginResult(username,jwt); 32 | } 33 | 34 | throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not authenticated"); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/controller/FrontendController.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.controller; 2 | 3 | import com.github.segator.proxylive.ProxyLiveUtils; 4 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 5 | import com.github.segator.proxylive.entity.ApiChannel; 6 | import com.github.segator.proxylive.entity.Channel; 7 | import com.github.segator.proxylive.service.AuthenticationService; 8 | import com.github.segator.proxylive.service.ChannelService; 9 | import com.github.segator.proxylive.service.EPGService; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.security.core.Authentication; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.bind.annotation.ResponseBody; 19 | 20 | import jakarta.servlet.http.HttpServletRequest; 21 | import jakarta.servlet.http.HttpServletResponse; 22 | import java.util.ArrayList; 23 | import java.util.Comparator; 24 | import java.util.List; 25 | 26 | @Controller 27 | @RequestMapping("/api") 28 | public class FrontendController { 29 | private final Logger logger = LoggerFactory.getLogger(FrontendController.class); 30 | 31 | private final ApplicationContext context; 32 | private final ProxyLiveConfiguration config; 33 | private final AuthenticationService authService; 34 | private final ChannelService channelService; 35 | private final EPGService epgService; 36 | 37 | public FrontendController(ApplicationContext context, ProxyLiveConfiguration config, AuthenticationService authService, ChannelService channelService, EPGService epgService) { 38 | this.context = context; 39 | this.config = config; 40 | this.authService = authService; 41 | this.channelService = channelService; 42 | this.epgService = epgService; 43 | } 44 | 45 | @RequestMapping(value = "channels", method = RequestMethod.GET) 46 | public @ResponseBody 47 | List generatePlaylist(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { 48 | List ApiChannelList=new ArrayList<>(); 49 | String requestBaseURL = ProxyLiveUtils.getBaseURL(request); 50 | 51 | for (Channel channel : channelService.getChannelList()) { 52 | ApiChannel apiChannel = ApiChannel.Create(channel); 53 | apiChannel.setChannelURL(String.format("%s/view/%s/%s/playlist.m3u8?token=%s",requestBaseURL, "1080p", channel.getId(),authentication.getCredentials())); 54 | if(channel.getLogoURL()!=null || channel.getLogoFile()!=null) { 55 | apiChannel.setLogoURL(String.format("%s/channel/%s/icon", requestBaseURL, channel.getId())); 56 | } 57 | ApiChannelList.add(apiChannel); 58 | } 59 | List channelsOrdered = new ArrayList(ApiChannelList); 60 | channelsOrdered.sort(new Comparator() { 61 | @Override 62 | public int compare(ApiChannel o1, ApiChannel o2) { 63 | return o1.getNumber().compareTo(o2.getNumber()); 64 | } 65 | }); 66 | return channelsOrdered; 67 | } 68 | @RequestMapping(value = "channel/{channelID}", method = RequestMethod.GET) 69 | public @ResponseBody 70 | ApiChannel getChannel(HttpServletRequest request, HttpServletResponse response,@PathVariable("profile") String channelID) { 71 | Channel channel = channelService.getChannelList().stream().filter(f->f.getId().equals(channelID)).findAny().get(); 72 | ApiChannel apiChannel = ApiChannel.Create(channel); 73 | if(channel.getEpgID()!=null){ 74 | apiChannel.setEpgProgramList(epgService.getEPGFromChannelID(channel.getEpgID())); 75 | } 76 | return apiChannel; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/controller/ViewController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.controller; 25 | 26 | import java.io.IOException; 27 | import org.springframework.stereotype.Controller; 28 | import org.springframework.web.bind.annotation.RequestMapping; 29 | 30 | /** 31 | * 32 | * @author Isaac Aymerich 33 | */ 34 | @Controller 35 | public class ViewController { 36 | @RequestMapping("/status") 37 | public String getStatus() throws IOException { 38 | return "status"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/ApiChannel.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.entity; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | 6 | public class ApiChannel { 7 | private Integer number; 8 | private String name; 9 | private String id; 10 | private String epgID; 11 | private String logoURL; 12 | private String channelURL; 13 | private List epgProgramList; 14 | private List categories; 15 | 16 | public Integer getNumber() { 17 | return number; 18 | } 19 | 20 | public void setNumber(Integer number) { 21 | this.number = number; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | public String getId() { 33 | return id; 34 | } 35 | 36 | public void setId(String id) { 37 | this.id = id; 38 | } 39 | 40 | public String getEpgID() { 41 | return epgID; 42 | } 43 | 44 | public void setEpgID(String epgID) { 45 | this.epgID = epgID; 46 | } 47 | 48 | public String getLogoURL() { 49 | return logoURL; 50 | } 51 | 52 | public void setLogoURL(String logoURL) { 53 | this.logoURL = logoURL; 54 | } 55 | 56 | public String getChannelURL() { 57 | return channelURL; 58 | } 59 | 60 | public void setChannelURL(String channelURL) { 61 | this.channelURL = channelURL; 62 | } 63 | 64 | public List getEpgProgramList() { 65 | return epgProgramList; 66 | } 67 | 68 | public void setEpgProgramList(List epgProgramList) { 69 | this.epgProgramList = epgProgramList; 70 | } 71 | 72 | public List getCategories() { 73 | return categories; 74 | } 75 | 76 | public void setCategories(List categories) { 77 | this.categories = categories; 78 | } 79 | 80 | public static ApiChannel Create(Channel channel) { 81 | ApiChannel apiChannel= new ApiChannel(); 82 | apiChannel.setNumber(channel.getNumber()); 83 | apiChannel.setCategories(channel.getCategories()); 84 | apiChannel.setEpgID(channel.getEpgID()); 85 | apiChannel.setId(channel.getId()); 86 | apiChannel.setName(channel.getName()); 87 | return apiChannel; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/Channel.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.File; 6 | import java.util.Comparator; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | @Data 12 | public class Channel { 13 | private Integer number; 14 | private String name; //used by EPG matching 15 | private String id; //used by EPG matching 16 | private String epgID; 17 | private String logoURL; 18 | private File logoFile; 19 | private String encryptionKey; 20 | private List categories; 21 | private List sources; 22 | private Map sourceHeaders=new HashMap<>(); 23 | private String ffmpegParameters=""; 24 | public Channel(){ 25 | 26 | } 27 | 28 | 29 | public ChannelSource getSourceByPriority(int priority){ 30 | Object[] orderedSources = getSources().stream().sorted(Comparator.comparing(ChannelSource::getPriority)).toArray(); 31 | if(priority> orderedSources.length){ 32 | return null; 33 | }else { 34 | return (ChannelSource) orderedSources[priority-1]; 35 | } 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/ChannelSource.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.entity; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ChannelSource { 7 | private String url; 8 | private String closeHook; 9 | private Integer priority; 10 | private String type="raw"; 11 | 12 | public ChannelSource(int priorty, String url,String type) { 13 | this.priority=priorty; 14 | this.url=url; 15 | this.type=type; 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/ClientInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.entity; 25 | 26 | import com.github.segator.proxylive.processor.IStreamProcessor; 27 | 28 | import java.net.InetAddress; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | import java.util.Objects; 32 | 33 | /** 34 | * 35 | * @author Isaac Aymerich 36 | */ 37 | public class ClientInfo { 38 | private String clientUser; 39 | private InetAddress ip; 40 | private GEOInfo geoInfo; 41 | private String browserInfo; 42 | private List streams; 43 | 44 | public ClientInfo(){ 45 | streams=new ArrayList(); 46 | } 47 | 48 | public InetAddress getIp() { 49 | return ip; 50 | } 51 | 52 | public void setIp(InetAddress ip) { 53 | this.ip = ip; 54 | } 55 | 56 | public String getBrowserInfo() { 57 | return browserInfo; 58 | } 59 | 60 | public String getClientUser() { 61 | return clientUser; 62 | } 63 | 64 | public void setClientUser(String clientUser) { 65 | this.clientUser = clientUser; 66 | } 67 | 68 | public GEOInfo getGeoInfo() { 69 | return geoInfo; 70 | } 71 | 72 | public void setGeoInfo(GEOInfo geoInfo) { 73 | this.geoInfo = geoInfo; 74 | } 75 | 76 | public void setBrowserInfo(String browserInfo) { 77 | this.browserInfo = browserInfo; 78 | } 79 | 80 | public List getStreams() { 81 | return streams; 82 | } 83 | 84 | public void setStreams(List streams) { 85 | this.streams = streams; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | int hash = 5; 91 | hash = 53 * hash + Objects.hashCode(this.clientUser); 92 | hash = 53 * hash + Objects.hashCode(this.ip); 93 | hash = 53 * hash + Objects.hashCode(this.browserInfo); 94 | return hash; 95 | } 96 | 97 | @Override 98 | public boolean equals(Object obj) { 99 | if (obj == null) { 100 | return false; 101 | } 102 | if (getClass() != obj.getClass()) { 103 | return false; 104 | } 105 | final ClientInfo other = (ClientInfo) obj; 106 | if (!Objects.equals(this.ip, other.ip)) { 107 | return false; 108 | } 109 | if (!Objects.equals(this.browserInfo, other.browserInfo)) { 110 | return false; 111 | } 112 | if (!Objects.equals(this.clientUser, other.clientUser)) { 113 | return false; 114 | } 115 | return true; 116 | } 117 | 118 | @Override 119 | public String toString() { 120 | String streamToStr="["; 121 | for (IStreamProcessor stream : streams) { 122 | streamToStr+=stream.toString()+","; 123 | } 124 | streamToStr = streamToStr.substring(0,streamToStr.length()-1); 125 | streamToStr+="]"; 126 | return "{user="+clientUser+", ip=" + ip + ", browserInfo=" + browserInfo + ", streams=" + streamToStr + '}'; 127 | } 128 | 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/EPGProgram.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class EPGProgram { 6 | private String epgID; 7 | private Date startTime; 8 | private Date endTime; 9 | private String title; 10 | private String desc; 11 | private String iconURL; 12 | 13 | public String getEpgID() { 14 | return epgID; 15 | } 16 | 17 | public void setEpgID(String epgID) { 18 | this.epgID = epgID; 19 | } 20 | 21 | public Date getStartTime() { 22 | return startTime; 23 | } 24 | 25 | public void setStartTime(Date startTime) { 26 | this.startTime = startTime; 27 | } 28 | 29 | public Date getEndTime() { 30 | return endTime; 31 | } 32 | 33 | public void setEndTime(Date endTime) { 34 | this.endTime = endTime; 35 | } 36 | 37 | public String getTitle() { 38 | return title; 39 | } 40 | 41 | public void setTitle(String title) { 42 | this.title = title; 43 | } 44 | 45 | public String getDesc() { 46 | return desc; 47 | } 48 | 49 | public void setDesc(String desc) { 50 | this.desc = desc; 51 | } 52 | 53 | public String getIconURL() { 54 | return iconURL; 55 | } 56 | 57 | public void setIconURL(String iconURL) { 58 | this.iconURL = iconURL; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/GEOInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.entity; 2 | 3 | import com.maxmind.geoip2.record.City; 4 | import com.maxmind.geoip2.record.Country; 5 | import com.maxmind.geoip2.record.Location; 6 | 7 | public class GEOInfo { 8 | private Country country; 9 | private City city; 10 | private Location location; 11 | 12 | 13 | public Country getCountry() { 14 | return country; 15 | } 16 | 17 | public void setCountry(Country country) { 18 | this.country = country; 19 | } 20 | 21 | public City getCity() { 22 | return city; 23 | } 24 | 25 | public void setCity(City city) { 26 | this.city = city; 27 | } 28 | 29 | public Location getLocation() { 30 | return location; 31 | } 32 | 33 | public void setLocation(Location location) { 34 | this.location = location; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/JSONClientInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.entity; 25 | 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * 31 | * @author Isaac Aymerich 32 | */ 33 | public class JSONClientInfo { 34 | private String user; 35 | private String ip; 36 | private String IPCountry; 37 | private String IPCity; 38 | private Double IPLatitude; 39 | private Double IPLongitude; 40 | private String browserInfo; 41 | private List streams; 42 | 43 | public class StreamProcessor { 44 | 45 | private String identifier; 46 | private String taskIdentifier; 47 | public StreamProcessor() { 48 | } 49 | 50 | public String getIdentifier() { 51 | return identifier; 52 | } 53 | 54 | public void setIdentifier(String identifier) { 55 | this.identifier = identifier; 56 | } 57 | 58 | public String getTaskIdentifier() { 59 | return taskIdentifier; 60 | } 61 | 62 | public void setTaskIdentifier(String taskIdentifier) { 63 | this.taskIdentifier = taskIdentifier; 64 | } 65 | 66 | 67 | } 68 | 69 | public String getUser() { 70 | return user; 71 | } 72 | 73 | public void setUser(String user) { 74 | this.user = user; 75 | } 76 | 77 | 78 | 79 | public String getIp() { 80 | return ip; 81 | } 82 | 83 | public void setIp(String ip) { 84 | this.ip = ip; 85 | } 86 | 87 | public String getBrowserInfo() { 88 | return browserInfo; 89 | } 90 | 91 | public void setBrowserInfo(String browserInfo) { 92 | this.browserInfo = browserInfo; 93 | } 94 | 95 | public List getStreams() { 96 | return streams; 97 | } 98 | 99 | public void setStreams(List streams) { 100 | this.streams = streams; 101 | } 102 | 103 | public String getIPCountry() { 104 | return IPCountry; 105 | } 106 | 107 | public void setIPCountry(String IPCountry) { 108 | this.IPCountry = IPCountry; 109 | } 110 | 111 | public String getIPCity() { 112 | return IPCity; 113 | } 114 | 115 | public void setIPCity(String IPCity) { 116 | this.IPCity = IPCity; 117 | } 118 | 119 | public Double getIPLatitude() { 120 | return IPLatitude; 121 | } 122 | 123 | public void setIPLatitude(Double IPLatitude) { 124 | this.IPLatitude = IPLatitude; 125 | } 126 | 127 | public Double getIPLongitude() { 128 | return IPLongitude; 129 | } 130 | 131 | public void setIPLongitude(Double IPLongitude) { 132 | this.IPLongitude = IPLongitude; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/JSONResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.entity; 25 | 26 | /** 27 | * 28 | * @author Isaac Aymerich 29 | */ 30 | public class JSONResponse { 31 | private Integer code; 32 | private String message; 33 | private String status; 34 | 35 | public Integer getCode() { 36 | return code; 37 | } 38 | 39 | public void setCode(Integer code) { 40 | this.code = code; 41 | } 42 | 43 | 44 | 45 | public String getMessage() { 46 | return message; 47 | } 48 | 49 | public void setMessage(String message) { 50 | this.message = message; 51 | } 52 | 53 | public String getStatus() { 54 | return status; 55 | } 56 | 57 | public void setStatus(String status) { 58 | this.status = status; 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/JSONTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.entity; 25 | 26 | /** 27 | * 28 | * @author Isaac Aymerich 29 | */ 30 | public class JSONTask { 31 | private String identifier; 32 | private String source; 33 | private String status; 34 | private String runningTime; 35 | 36 | public String getIdentifier() { 37 | return identifier; 38 | } 39 | 40 | public void setIdentifier(String identifier) { 41 | this.identifier = identifier; 42 | } 43 | 44 | public String getSource() { 45 | return source; 46 | } 47 | 48 | public void setSource(String source) { 49 | this.source = source; 50 | } 51 | 52 | public String getStatus() { 53 | return status; 54 | } 55 | 56 | public void setStatus(String status) { 57 | this.status = status; 58 | } 59 | 60 | public String getRunningTime() { 61 | return runningTime; 62 | } 63 | 64 | public void setRunningTime(String runningTime) { 65 | this.runningTime = runningTime; 66 | } 67 | 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/entity/LoginResult.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.entity; 2 | 3 | import lombok.Data; 4 | import lombok.NonNull; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Data 8 | @RequiredArgsConstructor 9 | public class LoginResult { 10 | @NonNull 11 | private String username; 12 | @NonNull 13 | private String jwt; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/helper/AuthorityRoles.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.helper; 2 | 3 | public enum AuthorityRoles { 4 | USER("USER"), 5 | SERVICE_ACCOUNT("SERVICE_ACCOUNT"), 6 | ALLOW_ENCODING("ENCODE_PULLER"), 7 | ADMIN("ADMIN"); 8 | private String authority; 9 | AuthorityRoles(String authority) { 10 | this.authority = authority; 11 | } 12 | 13 | public String getAuthority() { 14 | return authority; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/helper/JwtHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.helper; 2 | 3 | 4 | import com.github.segator.proxylive.config.JwtConfiguration; 5 | import io.jsonwebtoken.JwtBuilder; 6 | import io.jsonwebtoken.Jwts; 7 | import io.jsonwebtoken.SignatureAlgorithm; 8 | import org.springframework.security.core.GrantedAuthority; 9 | import org.springframework.security.core.authority.AuthorityUtils; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.time.Instant; 13 | import java.util.Calendar; 14 | import java.util.Date; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | 19 | @Component 20 | public class JwtHelper { 21 | private JwtConfiguration jwtConfiguration; 22 | 23 | public JwtHelper(JwtConfiguration jwtConfiguration) { 24 | this.jwtConfiguration = jwtConfiguration; 25 | } 26 | public String createJwtForClaims(String subject, List grantedAuthorities) { 27 | Calendar calendar = Calendar.getInstance(); 28 | calendar.setTimeInMillis(Instant.now().toEpochMilli()); 29 | calendar.add(Calendar.HOUR, jwtConfiguration.getExpireInHours()); 30 | return createJwtForClaims(subject,grantedAuthorities,calendar.getTime()); 31 | } 32 | public String createJwtForClaims(String subject, List grantedAuthorities,Date expirationDate) { 33 | JwtBuilder builder = Jwts.builder().setId("proxylive") 34 | .setSubject(subject) 35 | .claim("authorities",grantedAuthorities.stream() 36 | .map(GrantedAuthority::getAuthority) 37 | .collect(Collectors.toList())) 38 | .setIssuedAt(new Date(System.currentTimeMillis())); 39 | 40 | if(expirationDate!=null) { 41 | builder.setExpiration(expirationDate); 42 | } 43 | return builder.signWith(SignatureAlgorithm.HS512, 44 | jwtConfiguration.getSecret().getBytes()).compact(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/DirectHLSTranscoderStreamProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.processor; 2 | 3 | import com.github.segator.proxylive.entity.Channel; 4 | import com.github.segator.proxylive.stream.ClientBroadcastedInputStream; 5 | import com.github.segator.proxylive.tasks.*; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.ApplicationContext; 8 | 9 | import java.io.FileNotFoundException; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.net.URISyntaxException; 13 | import java.util.Objects; 14 | 15 | public class DirectHLSTranscoderStreamProcessor implements IStreamProcessor, IHLSStreamProcessor { 16 | 17 | @Autowired 18 | private ProcessorTasks tasks; 19 | @Autowired 20 | private ApplicationContext context; 21 | 22 | 23 | private final Channel channel; 24 | private final String profile; 25 | private ClientBroadcastedInputStream pip; 26 | private HLSDirectTask hlsDirectTask; 27 | 28 | public DirectHLSTranscoderStreamProcessor(Channel channel, String profile) { 29 | this.channel = channel; 30 | this.profile = profile; 31 | } 32 | 33 | @Override 34 | public void start() throws Exception { 35 | HLSDirectTask streamingDownloaderTask = (HLSDirectTask) context.getBean("HLSDirectTask", channel,profile); 36 | synchronized (tasks) { 37 | hlsDirectTask = (HLSDirectTask) tasks.getTask(streamingDownloaderTask); 38 | if (hlsDirectTask == null) { 39 | tasks.runTask(streamingDownloaderTask); 40 | hlsDirectTask = streamingDownloaderTask; 41 | } 42 | } 43 | } 44 | 45 | public String getChannel() { 46 | return channel.getId(); 47 | } 48 | 49 | @Override 50 | public void stop(boolean force) throws IOException { 51 | tasks.killTask(hlsDirectTask); 52 | } 53 | 54 | 55 | 56 | @Override 57 | public boolean isConnected() { 58 | synchronized (tasks) { 59 | return isTaskRunning(); 60 | } 61 | } 62 | 63 | private boolean isTaskRunning() { 64 | return hlsDirectTask != null && !hlsDirectTask.isCrashed(); 65 | } 66 | 67 | @Override 68 | public IStreamTask getTask() { 69 | return hlsDirectTask; 70 | } 71 | 72 | @Override 73 | public int hashCode() { 74 | int hash = 3; 75 | hash = 89 * hash + Objects.hashCode(this.hlsDirectTask); 76 | return hash; 77 | } 78 | 79 | @Override 80 | public boolean equals(Object obj) { 81 | if (obj == null) { 82 | return false; 83 | } 84 | if (getClass() != obj.getClass()) { 85 | return false; 86 | } 87 | final DirectHLSTranscoderStreamProcessor other = (DirectHLSTranscoderStreamProcessor) obj; 88 | if (!Objects.equals(this.hlsDirectTask, other.hlsDirectTask)) { 89 | return false; 90 | } 91 | return true; 92 | } 93 | 94 | @Override 95 | public String getIdentifier() { 96 | return channel.getName(); 97 | } 98 | 99 | @Override 100 | public InputStream getPlayList() throws IOException, URISyntaxException { 101 | return hlsDirectTask.getPlayList(); 102 | } 103 | 104 | @Override 105 | public InputStream getSegment(String segment) throws FileNotFoundException { 106 | return hlsDirectTask.getSegment(segment); 107 | } 108 | 109 | @Override 110 | public long getSegmentSize(String segment) throws FileNotFoundException { 111 | return hlsDirectTask.getSegmentSize(segment); 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return "DirectHLSTranscoderStreamProcessor{" + "channel=" + channel + ", profile=" + profile + '}'; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/DirectTranscoderStreamProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.processor; 7 | 8 | import com.github.segator.proxylive.entity.Channel; 9 | import com.github.segator.proxylive.stream.ClientBroadcastedInputStream; 10 | import com.github.segator.proxylive.tasks.DirectTranscodeTask; 11 | import com.github.segator.proxylive.tasks.IStreamTask; 12 | import com.github.segator.proxylive.tasks.ProcessorTasks; 13 | import java.io.IOException; 14 | import java.util.Objects; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.context.ApplicationContext; 17 | 18 | /** 19 | * 20 | * @author isaac 21 | */ 22 | public class DirectTranscoderStreamProcessor implements IStreamMultiplexerProcessor, ISourceStream { 23 | 24 | @Autowired 25 | private ProcessorTasks tasks; 26 | @Autowired 27 | private ApplicationContext context; 28 | 29 | 30 | private final Channel channel; 31 | private final String profile; 32 | private ClientBroadcastedInputStream pip; 33 | private DirectTranscodeTask streamingDownloaderRunningTask; 34 | 35 | public DirectTranscoderStreamProcessor(Channel channel, String profile) { 36 | this.channel = channel; 37 | this.profile = profile; 38 | } 39 | 40 | @Override 41 | public void start() throws Exception { 42 | DirectTranscodeTask streamingDownloaderTask = (DirectTranscodeTask) context.getBean("DirectTranscodeTask", channel,profile); 43 | synchronized (tasks) { 44 | streamingDownloaderRunningTask = (DirectTranscodeTask) tasks.getTask(streamingDownloaderTask); 45 | if (streamingDownloaderRunningTask == null) { 46 | tasks.runTask(streamingDownloaderTask); 47 | streamingDownloaderRunningTask = streamingDownloaderTask; 48 | } 49 | //pip = streamingDownloaderRunningTask.getMultiplexer().getClientInputStream("transcoded direct cli"); 50 | pip = streamingDownloaderRunningTask.getMultiplexer().getConsumer("transcoded direct cli"); 51 | } 52 | } 53 | 54 | public String getChannel() { 55 | return channel.getId(); 56 | } 57 | 58 | @Override 59 | public void stop(boolean force) throws IOException { 60 | synchronized (tasks) { 61 | streamingDownloaderRunningTask.getMultiplexer().flush(); 62 | 63 | streamingDownloaderRunningTask.getMultiplexer().removeClientConsumer(pip); 64 | 65 | if (force || streamingDownloaderRunningTask.getMultiplexer().getClientsList().isEmpty()) { 66 | tasks.killTask(streamingDownloaderRunningTask); 67 | } 68 | try { 69 | pip.close(); 70 | } catch (Exception ex) { 71 | } 72 | } 73 | } 74 | 75 | @Override 76 | public ClientBroadcastedInputStream getMultiplexedInputStream() { 77 | synchronized (tasks) { 78 | return pip; 79 | } 80 | } 81 | 82 | @Override 83 | public boolean isConnected() { 84 | synchronized (tasks) { 85 | boolean connected = isTaskRunning(); 86 | if (!connected) { 87 | try { 88 | Thread.sleep(5000); 89 | } catch (InterruptedException ex) { 90 | return false; 91 | } 92 | return isTaskRunning(); 93 | } 94 | return true; 95 | } 96 | } 97 | 98 | private boolean isTaskRunning() { 99 | return streamingDownloaderRunningTask != null && !streamingDownloaderRunningTask.isTerminated() && !streamingDownloaderRunningTask.isCrashed(); 100 | } 101 | 102 | @Override 103 | public IStreamTask getTask() { 104 | return streamingDownloaderRunningTask; 105 | } 106 | 107 | @Override 108 | public int hashCode() { 109 | int hash = 3; 110 | hash = 89 * hash + Objects.hashCode(this.streamingDownloaderRunningTask); 111 | return hash; 112 | } 113 | 114 | @Override 115 | public boolean equals(Object obj) { 116 | if (obj == null) { 117 | return false; 118 | } 119 | if (getClass() != obj.getClass()) { 120 | return false; 121 | } 122 | final DirectTranscoderStreamProcessor other = (DirectTranscoderStreamProcessor) obj; 123 | if (!Objects.equals(this.streamingDownloaderRunningTask, other.streamingDownloaderRunningTask)) { 124 | return false; 125 | } 126 | return true; 127 | } 128 | 129 | @Override 130 | public String getIdentifier() { 131 | return channel.getName(); 132 | } 133 | 134 | @Override 135 | public String toString() { 136 | return "DirectTranscoderStreamProcessor{" + "channel=" + channel + ", profile=" + profile + '}'; 137 | } 138 | 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/HttpSoureStreamProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.processor; 25 | 26 | import com.github.segator.proxylive.entity.Channel; 27 | import com.github.segator.proxylive.tasks.HttpDownloaderTask; 28 | import com.github.segator.proxylive.stream.ClientBroadcastedInputStream; 29 | import com.github.segator.proxylive.tasks.IStreamTask; 30 | import com.github.segator.proxylive.tasks.ProcessorTasks; 31 | import java.io.IOException; 32 | import java.util.Objects; 33 | import org.springframework.beans.factory.annotation.Autowired; 34 | import org.springframework.context.ApplicationContext; 35 | 36 | /** 37 | * 38 | * @author Isaac Aymerich 39 | */ 40 | public class HttpSoureStreamProcessor implements IStreamMultiplexerProcessor, ISourceStream { 41 | 42 | @Autowired 43 | private ProcessorTasks tasks; 44 | @Autowired 45 | private ApplicationContext context; 46 | 47 | 48 | private final Channel channel; 49 | private ClientBroadcastedInputStream pip; 50 | private HttpDownloaderTask streamingDownloaderRunningTask; 51 | 52 | public HttpSoureStreamProcessor(Channel channel) { 53 | this.channel = channel; 54 | } 55 | 56 | @Override 57 | public void start() throws Exception { 58 | HttpDownloaderTask streamingDownloaderTask = (HttpDownloaderTask) context.getBean("HttpDownloaderTask", channel); 59 | synchronized (tasks) { 60 | streamingDownloaderRunningTask = (HttpDownloaderTask) tasks.getTask(streamingDownloaderTask); 61 | if (streamingDownloaderRunningTask == null) { 62 | tasks.runTask(streamingDownloaderTask); 63 | streamingDownloaderRunningTask = streamingDownloaderTask; 64 | } 65 | pip = streamingDownloaderRunningTask.getMultiplexer().getConsumer("http cli"); 66 | } 67 | } 68 | 69 | public String getChannel() { 70 | return channel.getId(); 71 | } 72 | 73 | @Override 74 | public void stop(boolean force) throws IOException { 75 | synchronized (tasks) { 76 | streamingDownloaderRunningTask.getMultiplexer().flush(); 77 | 78 | streamingDownloaderRunningTask.getMultiplexer().removeClientConsumer(pip); 79 | 80 | if (force || streamingDownloaderRunningTask.getMultiplexer().getClientsList().isEmpty()) { 81 | tasks.killTask(streamingDownloaderRunningTask); 82 | } 83 | try { 84 | pip.close(); 85 | } catch (Exception ex) { 86 | } 87 | } 88 | } 89 | 90 | @Override 91 | public ClientBroadcastedInputStream getMultiplexedInputStream() { 92 | synchronized (tasks) { 93 | return pip; 94 | } 95 | } 96 | 97 | @Override 98 | public boolean isConnected() { 99 | synchronized (tasks) { 100 | boolean connected = isTaskRunning(); 101 | if (!connected) { 102 | try { 103 | Thread.sleep(5000); 104 | } catch (InterruptedException ex) { 105 | return false; 106 | } 107 | return isTaskRunning(); 108 | } 109 | return true; 110 | } 111 | } 112 | 113 | private boolean isTaskRunning() { 114 | return streamingDownloaderRunningTask != null && !streamingDownloaderRunningTask.isTerminated() && !streamingDownloaderRunningTask.isCrashed(); 115 | } 116 | 117 | @Override 118 | public IStreamTask getTask() { 119 | return streamingDownloaderRunningTask; 120 | } 121 | 122 | @Override 123 | public int hashCode() { 124 | int hash = 3; 125 | hash = 89 * hash + Objects.hashCode(this.streamingDownloaderRunningTask); 126 | return hash; 127 | } 128 | 129 | @Override 130 | public boolean equals(Object obj) { 131 | if (obj == null) { 132 | return false; 133 | } 134 | if (getClass() != obj.getClass()) { 135 | return false; 136 | } 137 | final HttpSoureStreamProcessor other = (HttpSoureStreamProcessor) obj; 138 | if (!Objects.equals(this.streamingDownloaderRunningTask, other.streamingDownloaderRunningTask)) { 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | @Override 145 | public String getIdentifier() { 146 | return channel.getName(); 147 | } 148 | 149 | @Override 150 | public String toString() { 151 | return "HttpSoureStreamProcessor{" + "channel=" + channel + '}'; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/IHLSStreamProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.processor; 25 | 26 | import java.io.FileNotFoundException; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.net.URISyntaxException; 30 | 31 | /** 32 | * 33 | * @author Isaac Aymerich 34 | */ 35 | public interface IHLSStreamProcessor { 36 | 37 | public InputStream getPlayList() throws IOException, URISyntaxException; 38 | 39 | public InputStream getSegment(String segment) throws FileNotFoundException; 40 | 41 | public long getSegmentSize(String segment) throws FileNotFoundException; 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/ISourceStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.processor; 25 | 26 | import java.io.IOException; 27 | 28 | /** 29 | * 30 | * @author Isaac Aymerich 31 | */ 32 | public interface ISourceStream { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/IStreamMultiplexerProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.processor; 25 | 26 | import com.github.segator.proxylive.stream.ClientBroadcastedInputStream; 27 | 28 | /** 29 | * 30 | * @author Isaac Aymerich 31 | */ 32 | public interface IStreamMultiplexerProcessor extends IStreamProcessor { 33 | 34 | public ClientBroadcastedInputStream getMultiplexedInputStream(); 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/IStreamProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.processor; 25 | 26 | import com.github.segator.proxylive.tasks.IStreamTask; 27 | import java.io.IOException; 28 | 29 | /** 30 | * 31 | * @author Isaac Aymerich 32 | */ 33 | public interface IStreamProcessor { 34 | 35 | public void start() throws Exception; 36 | 37 | public void stop(boolean force) throws IOException; 38 | 39 | 40 | 41 | public boolean isConnected(); 42 | 43 | public IStreamTask getTask(); 44 | 45 | public String getIdentifier(); 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/RemoteTranscodeStreamProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.processor; 25 | 26 | import com.github.segator.proxylive.config.RemoteTranscoder; 27 | import com.github.segator.proxylive.stream.ClientBroadcastedInputStream; 28 | import com.github.segator.proxylive.tasks.IStreamTask; 29 | import com.github.segator.proxylive.tasks.ProcessorTasks; 30 | import com.github.segator.proxylive.tasks.RemoteTranscodeTask; 31 | import org.springframework.beans.factory.annotation.Autowired; 32 | import org.springframework.context.ApplicationContext; 33 | 34 | import java.io.IOException; 35 | import java.util.Objects; 36 | 37 | /** 38 | * 39 | * @author Isaac Aymerich 40 | */ 41 | public class RemoteTranscodeStreamProcessor implements IStreamMultiplexerProcessor, ISourceStream { 42 | 43 | @Autowired 44 | private ProcessorTasks tasks; 45 | @Autowired 46 | private ApplicationContext context; 47 | 48 | 49 | private final RemoteTranscoder transcoder; 50 | private final String channelID; 51 | private ClientBroadcastedInputStream pip; 52 | private RemoteTranscodeTask streamingDownloaderRunningTask; 53 | 54 | public RemoteTranscodeStreamProcessor(String channelID ,RemoteTranscoder transcoder) { 55 | this.transcoder = transcoder; 56 | this.channelID = channelID; 57 | } 58 | 59 | @Override 60 | public void start() throws Exception { 61 | RemoteTranscodeTask streamingRemoteTranscoderTask = (RemoteTranscodeTask) context.getBean("RemoteTranscodeTask", channelID,transcoder); 62 | synchronized (tasks) { 63 | streamingDownloaderRunningTask = (RemoteTranscodeTask) tasks.getTask(streamingRemoteTranscoderTask); 64 | if (streamingDownloaderRunningTask == null) { 65 | tasks.runTask(streamingRemoteTranscoderTask); 66 | streamingDownloaderRunningTask = streamingRemoteTranscoderTask; 67 | } 68 | pip = streamingDownloaderRunningTask.getMultiplexer().getConsumer("http cli"); 69 | } 70 | } 71 | 72 | 73 | @Override 74 | public void stop(boolean force) throws IOException { 75 | synchronized (tasks) { 76 | streamingDownloaderRunningTask.getMultiplexer().flush(); 77 | 78 | streamingDownloaderRunningTask.getMultiplexer().removeClientConsumer(pip); 79 | 80 | if (force || streamingDownloaderRunningTask.getMultiplexer().getClientsList().isEmpty()) { 81 | tasks.killTask(streamingDownloaderRunningTask); 82 | } 83 | try { 84 | pip.close(); 85 | } catch (Exception ex) { 86 | } 87 | } 88 | } 89 | 90 | @Override 91 | public ClientBroadcastedInputStream getMultiplexedInputStream() { 92 | synchronized (tasks) { 93 | return pip; 94 | } 95 | } 96 | 97 | @Override 98 | public boolean isConnected() { 99 | synchronized (tasks) { 100 | boolean connected = isTaskRunning(); 101 | if (!connected) { 102 | try { 103 | Thread.sleep(5000); 104 | } catch (InterruptedException ex) { 105 | return false; 106 | } 107 | return isTaskRunning(); 108 | } 109 | return true; 110 | } 111 | } 112 | 113 | private boolean isTaskRunning() { 114 | return streamingDownloaderRunningTask != null && !streamingDownloaderRunningTask.isTerminated() && !streamingDownloaderRunningTask.isCrashed(); 115 | } 116 | 117 | @Override 118 | public IStreamTask getTask() { 119 | return streamingDownloaderRunningTask; 120 | } 121 | 122 | @Override 123 | public int hashCode() { 124 | int hash = 3; 125 | hash = 89 * hash + Objects.hashCode(this.streamingDownloaderRunningTask); 126 | return hash; 127 | } 128 | 129 | @Override 130 | public boolean equals(Object obj) { 131 | if (obj == null) { 132 | return false; 133 | } 134 | if (getClass() != obj.getClass()) { 135 | return false; 136 | } 137 | final RemoteTranscodeStreamProcessor other = (RemoteTranscodeStreamProcessor) obj; 138 | if (!Objects.equals(this.streamingDownloaderRunningTask, other.streamingDownloaderRunningTask)) { 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | @Override 145 | public String getIdentifier() { 146 | return "remote_" +channelID + "_"+ transcoder.getEndpoint() ; 147 | } 148 | 149 | @Override 150 | public String toString() { 151 | return "RemoteTranscodeStreamProcessor{" + 152 | "transcoder=" + transcoder + 153 | ", channelID='" + channelID + '\'' + 154 | '}'; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/processor/StreamProcessorFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.processor; 25 | 26 | import com.github.segator.proxylive.ProxyLiveConstants; 27 | 28 | import java.rmi.Remote; 29 | import java.util.Base64; 30 | import java.util.Date; 31 | import java.util.List; 32 | 33 | import com.github.segator.proxylive.config.FFMpegProfile; 34 | import com.github.segator.proxylive.config.RemoteTranscoder; 35 | import com.github.segator.proxylive.entity.Channel; 36 | import com.github.segator.proxylive.entity.ChannelSource; 37 | import com.github.segator.proxylive.profiler.FFmpegProfilerService; 38 | import org.springframework.beans.factory.annotation.Autowired; 39 | import org.springframework.context.ApplicationContext; 40 | import org.springframework.context.annotation.Bean; 41 | import org.springframework.context.annotation.Configuration; 42 | import org.springframework.context.annotation.Scope; 43 | 44 | /** 45 | * 46 | * @author Isaac Aymerich 47 | */ 48 | @Configuration 49 | public class StreamProcessorFactory { 50 | 51 | private final ApplicationContext context; 52 | private final FFmpegProfilerService ffmpegProfileService; 53 | 54 | public StreamProcessorFactory(ApplicationContext context, FFmpegProfilerService ffmpegProfileService) { 55 | this.context = context; 56 | this.ffmpegProfileService = ffmpegProfileService; 57 | } 58 | 59 | @Bean 60 | @Scope(value = "prototype") 61 | public IStreamProcessor StreamProcessor(int mode, String clientIdentifier, Channel channel, String profile) { 62 | IStreamProcessor streamProcessor = null; 63 | switch (mode) { 64 | case ProxyLiveConstants.HLS_MODE: 65 | streamProcessor = (IStreamProcessor) context.getBean("DirectHLSTranscoderStreamProcessor", channel, profile); 66 | break; 67 | case ProxyLiveConstants.STREAM_MODE: 68 | if(profile!=null && !profile.equals("raw")){ 69 | FFMpegProfile ffmpegProfile = ffmpegProfileService.getProfile(profile); 70 | if(ffmpegProfile.isLocalTranscoding()) { 71 | streamProcessor = (IStreamProcessor) context.getBean("DirectTranscodedStreamProcessor", channel, profile); 72 | break; 73 | }else{ 74 | RemoteTranscoder remoteTranscoder = RemoteTranscoder.CreateFrom(ffmpegProfile.getTranscoder()); 75 | if(remoteTranscoder.getProfile()==null){ 76 | remoteTranscoder.setProfile(profile); 77 | } 78 | streamProcessor = (IStreamProcessor) context.getBean("RemoteTranscodeStreamProcessor", channel.getId(), remoteTranscoder); 79 | } 80 | }else { 81 | streamProcessor = (HttpSoureStreamProcessor) context.getBean("HttpSoureStreamProcessor", channel); 82 | } 83 | break; 84 | } 85 | return streamProcessor; 86 | 87 | } 88 | 89 | @Bean 90 | @Scope(value = "prototype") 91 | public IStreamProcessor HttpSoureStreamProcessor(Channel channel) { 92 | return new HttpSoureStreamProcessor(channel); 93 | } 94 | 95 | @Bean 96 | @Scope(value = "prototype") 97 | public IStreamProcessor DirectTranscodedStreamProcessor(Channel channel, String profile) { 98 | return new DirectTranscoderStreamProcessor(channel, profile); 99 | } 100 | @Bean 101 | @Scope(value = "prototype") 102 | public IStreamProcessor RemoteTranscodeStreamProcessor(String channelID, RemoteTranscoder transcoder) { 103 | return new RemoteTranscodeStreamProcessor(channelID, transcoder); 104 | } 105 | 106 | @Bean 107 | @Scope(value = "prototype") 108 | public IStreamProcessor DirectHLSTranscoderStreamProcessor(Channel channel, String profile) { 109 | return new DirectHLSTranscoderStreamProcessor(channel, profile); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/profiler/FFmpegProfilerService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.profiler; 25 | 26 | import com.github.segator.proxylive.config.FFMpegProfile; 27 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 28 | import org.springframework.beans.factory.annotation.Autowired; 29 | import org.springframework.stereotype.Service; 30 | 31 | import java.nio.file.Path; 32 | import java.nio.file.Paths; 33 | 34 | /** 35 | * 36 | * @author Isaac Aymerich 37 | */ 38 | @Service 39 | public class FFmpegProfilerService { 40 | @Autowired 41 | private ProxyLiveConfiguration config; 42 | 43 | 44 | public FFMpegProfile getProfile(String profile) { 45 | for (FFMpegProfile ffmpegProfile : config.getFfmpeg().getProfiles()) { 46 | if (ffmpegProfile.getAlias().equals(profile)) { 47 | return ffmpegProfile; 48 | } 49 | } 50 | return null; 51 | } 52 | 53 | public String getTranscodeParameters(String profile) { 54 | return getProfile(profile).getParameters(); 55 | } 56 | 57 | public Path getHLSTemporalPath(String taskIdentifier) { 58 | return Paths.get(config.getFfmpeg().getHls().getTempPath(),taskIdentifier.replace(":", "_")); 59 | } 60 | 61 | public String getSegmentFormat() { 62 | return getSegmentName() + getSegmentDate("FFmpeg") + "." + getSegmentFileFormat(); 63 | } 64 | 65 | public String getSegmentFileFormat() { 66 | return "ts"; 67 | } 68 | 69 | public String getSegmentName() { 70 | return "segment_"; 71 | } 72 | 73 | public String getSegmentDate(String type) { 74 | switch (type) { 75 | case "FFmpeg": 76 | return "%Y_%m_%d_%H_%M_%S"; 77 | 78 | case "SimpleDateFormat": 79 | return "yyyy_MM_dd_HH_mm_ss"; 80 | } 81 | return null; 82 | } 83 | 84 | public String getHLSParameters(String taskIdentifier) { 85 | Path tempFolder = getHLSTemporalPath(taskIdentifier); 86 | return config.getFfmpeg().getHls().getParameters() + " " + tempFolder.toString() + "/playlist.m3u8"; 87 | } 88 | 89 | public String getFFMpegExecutable() { 90 | return config.getFfmpeg().getPath(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/AuthenticationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.service; 7 | 8 | import com.github.segator.proxylive.helper.AuthorityRoles; 9 | import org.springframework.security.core.GrantedAuthority; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 15 | * @author isaac 16 | */ 17 | 18 | public interface AuthenticationService { 19 | public boolean loginUser(String user, String password) throws Exception; 20 | public List getUserRoles(String user); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/AuthenticationServiceFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.service; 7 | 8 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | 13 | /** 14 | * 15 | * @author isaac 16 | */ 17 | @Configuration 18 | public class AuthenticationServiceFactory { 19 | private final ProxyLiveConfiguration config; 20 | 21 | public AuthenticationServiceFactory(ProxyLiveConfiguration config) { 22 | this.config = config; 23 | } 24 | 25 | @Bean 26 | public AuthenticationService createAuthenticationService() { 27 | if(config.getAuthentication()==null){ 28 | return new WithoutAuthenticationService(); 29 | } 30 | if(config.getAuthentication().getLdap()!=null){ 31 | return new LDAPAuthenticationService(config); 32 | }else if(config.getAuthentication().getPlex()!=null){ 33 | return new PlexAuthenticationService(config); 34 | }else{ 35 | return new WithoutAuthenticationService(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/ChannelService.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | import com.github.segator.proxylive.entity.Channel; 4 | 5 | import java.util.List; 6 | 7 | public interface ChannelService { 8 | public List getChannelList(); 9 | 10 | public Channel getChannelByID(String channelID); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/ChannelServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class ChannelServiceFactory { 9 | 10 | private final ProxyLiveConfiguration config; 11 | 12 | public ChannelServiceFactory(ProxyLiveConfiguration config) { 13 | this.config = config; 14 | } 15 | 16 | @Bean 17 | public ChannelService createChannelService() { 18 | switch(config.getSource().getChannels().getType()){ 19 | case "tvheadend": 20 | return new ChannelTVHeadendService(); 21 | case "json": 22 | return new ChannelURLService(); 23 | case "m3u8": 24 | return new ChannelM3u8Service(); 25 | } 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/ChannelTVHeadendService.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 4 | import com.github.segator.proxylive.entity.Channel; 5 | import com.github.segator.proxylive.entity.ChannelSource; 6 | import jakarta.annotation.PostConstruct; 7 | import jakarta.annotation.PreDestroy; 8 | import org.apache.commons.io.FileUtils; 9 | import org.apache.commons.io.IOUtils; 10 | import org.json.simple.JSONArray; 11 | import org.json.simple.JSONObject; 12 | import org.json.simple.parser.JSONParser; 13 | import org.json.simple.parser.ParseException; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.scheduling.annotation.Scheduled; 18 | 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.net.HttpURLConnection; 24 | import java.net.MalformedURLException; 25 | import java.net.ProtocolException; 26 | import java.net.URL; 27 | import java.nio.file.Files; 28 | import java.nio.file.Path; 29 | import java.nio.file.Paths; 30 | import java.util.*; 31 | 32 | public class ChannelTVHeadendService implements ChannelService { 33 | private final Logger logger = LoggerFactory.getLogger(ChannelTVHeadendService.class); 34 | @Autowired 35 | private ProxyLiveConfiguration config; 36 | 37 | 38 | private JSONArray cachedChannelList; 39 | private JSONArray cachedChannelTags; 40 | private List channels; 41 | private File tempLogoFilePath; 42 | private long lastUpdate=0; 43 | 44 | 45 | @Override 46 | public List getChannelList() { 47 | return channels; 48 | } 49 | 50 | @Override 51 | public Channel getChannelByID(String channelID) 52 | { 53 | Optional channelOptional = channels.stream().filter(ch -> ch.getId().equals(channelID)).findFirst(); 54 | if(channelOptional.isPresent()){ 55 | return channelOptional.get(); 56 | }else{ 57 | return null; 58 | } 59 | } 60 | 61 | 62 | private void getTvheadendData() throws ProtocolException, IOException, MalformedURLException, ParseException { 63 | cachedChannelList = (JSONArray) getTvheadendResponse("api/channel/grid?start=0&limit=5000").get("entries"); 64 | cachedChannelTags = (JSONArray) getTvheadendResponse("api/channeltag/list").get("entries"); 65 | } 66 | 67 | private List buildChannels() throws Exception { 68 | logger.info("Updating Channel Info"); 69 | List channels=new ArrayList(); 70 | Path piconsPath = Files.createTempDirectory("proxylivePicons"); 71 | for (Object ochannel : cachedChannelList) { 72 | Channel channel = new Channel(); 73 | channels.add(channel); 74 | JSONObject channelObject = (JSONObject) ochannel; 75 | //channel ID 76 | channel.setId((String)channelObject.get("uuid")); 77 | //tvheadend channel match ID with EPG extracted from tvh 78 | channel.setEpgID(channel.getId()); 79 | logger.debug("Updating Channel:"+channel.getId()); 80 | 81 | //Channel number 82 | String channelNumber=""; 83 | if(channelObject.get("number")!=null){ 84 | channel.setNumber(Integer.valueOf(channelObject.get("number").toString())); 85 | } 86 | 87 | 88 | //Channel Name 89 | channel.setName((String)channelObject.get("name")); 90 | 91 | //Channel categories 92 | JSONArray categories = (JSONArray)channelObject.get("tags"); 93 | List categoriesNames=new ArrayList(); 94 | for (Object oCategory:categories) { 95 | String category =(String)oCategory; 96 | categoriesNames.add(getCategoryName(cachedChannelTags,category)); 97 | } 98 | channel.setCategories(categoriesNames); 99 | 100 | String iconURL = (String)channelObject.get("icon_public_url"); 101 | if (iconURL!= null) { 102 | try { 103 | HttpURLConnection connection = getURLConnection(iconURL); 104 | if (connection.getResponseCode() != 404 && connection.getResponseCode() != 1) { 105 | File logoFile = Paths.get(piconsPath.toString(), channel.getId() + ".png").toFile(); 106 | channel.setLogoFile(logoFile); 107 | FileOutputStream fos = new FileOutputStream(logoFile); 108 | IOUtils.copy(connection.getInputStream(), fos); 109 | fos.flush(); 110 | connection.getInputStream().close(); 111 | fos.close(); 112 | connection.disconnect(); 113 | } 114 | }catch(Exception ex){ 115 | 116 | } 117 | } 118 | 119 | //Channel URL 120 | channel.setSources(new ArrayList()); 121 | channel.getSources().add(new ChannelSource(1,config.getSource().getTvheadendURL()+"/stream/channel/"+channel.getId(),"raw")); 122 | } 123 | if (tempLogoFilePath != null && tempLogoFilePath.exists()) { 124 | FileUtils.deleteDirectory(tempLogoFilePath); 125 | } 126 | tempLogoFilePath = piconsPath.toFile(); 127 | logger.info("Updating Channel Info Completed"); 128 | //ObjectMapper mapper = new ObjectMapper(); 129 | //mapper.enable(SerializationFeature.INDENT_OUTPUT); 130 | //mapper.writeValue(new File("D:\\file.json"), channels); 131 | 132 | return channels; 133 | } 134 | 135 | @PreDestroy 136 | private void cleanup() throws IOException { 137 | logger.debug("cleaning picons directory"); 138 | if (tempLogoFilePath != null && tempLogoFilePath.exists()) { 139 | FileUtils.deleteDirectory(tempLogoFilePath); 140 | } 141 | } 142 | 143 | private String getCategoryName(JSONArray tags, String category) { 144 | for (Object otag : tags) { 145 | JSONObject tag = (JSONObject) otag; 146 | if(tag.get("key").equals(category)){ 147 | return (String)tag.get("val"); 148 | } 149 | } 150 | return "channels"; 151 | } 152 | 153 | private JSONObject getTvheadendResponse(String request) throws MalformedURLException, ProtocolException, IOException, ParseException { 154 | JSONParser jsonParser = new JSONParser(); 155 | HttpURLConnection connection = getURLConnection(request); 156 | if (connection.getResponseCode() != 200) { 157 | throw new IOException("Error on open stream:" + request); 158 | } 159 | JSONObject returnObject = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream(), "UTF-8")); 160 | connection.disconnect(); 161 | return returnObject; 162 | 163 | } 164 | 165 | 166 | private HttpURLConnection getURLConnection(String request) throws MalformedURLException, IOException { 167 | URL tvheadendURL = new URL(config.getSource().getTvheadendURL() + "/" + request); 168 | HttpURLConnection connection = (HttpURLConnection) tvheadendURL.openConnection(); 169 | connection.setReadTimeout(10000); 170 | if (tvheadendURL.getUserInfo() != null) { 171 | String basicAuth = "Basic " + new String(Base64.getEncoder().encode(tvheadendURL.getUserInfo().getBytes())); 172 | connection.setRequestProperty("Authorization", basicAuth); 173 | } 174 | connection.setRequestMethod("GET"); 175 | connection.connect(); 176 | return connection; 177 | } 178 | @Scheduled(fixedDelay = 60 * 1000) //Every Minute 179 | @PostConstruct 180 | public void getDataFromTvheadend() throws Exception { 181 | if(new Date().getTime()-lastUpdate>+(config.getSource().getEpg().getRefresh()*1000)) { 182 | //Get Channels, Tags 183 | getTvheadendData(); 184 | channels = buildChannels(); 185 | lastUpdate=new Date().getTime(); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/ConfigurationService.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class ConfigurationService { 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/EPGService.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 4 | import com.github.segator.proxylive.entity.EPGProgram; 5 | import jakarta.annotation.PostConstruct; 6 | import org.apache.commons.io.IOUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.scheduling.annotation.Scheduled; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.net.HttpURLConnection; 17 | import java.net.MalformedURLException; 18 | import java.net.URL; 19 | import java.util.ArrayList; 20 | import java.util.Base64; 21 | import java.util.Date; 22 | import java.util.List; 23 | import java.util.zip.GZIPInputStream; 24 | 25 | @Service 26 | public class EPGService { 27 | private final Logger logger = LoggerFactory.getLogger(EPGService.class); 28 | private File tempEPGFile; 29 | 30 | private final ProxyLiveConfiguration config; 31 | private long lastUpdate=0; 32 | 33 | public EPGService(ProxyLiveConfiguration config) { 34 | this.config = config; 35 | } 36 | 37 | @Scheduled(fixedDelay = 600 * 1000) //Every 100 Minute 38 | @PostConstruct 39 | private void buildEPG() throws Exception { 40 | if(config.getSource().getEpg()==null || config.getSource().getEpg().getUrl()==null ){ 41 | return; 42 | } 43 | if(new Date().getTime()-lastUpdate>+(config.getSource().getEpg().getRefresh()*1000)) { 44 | logger.info("Refreshing EPG"); 45 | HttpURLConnection connection = getURLConnection(config.getSource().getEpg().getUrl()); 46 | if (connection.getResponseCode() != 200) { 47 | return; 48 | } 49 | InputStream channel = connection.getInputStream(); 50 | if(config.getSource().getEpg().getUrl().endsWith("gz")){ 51 | channel = new GZIPInputStream(channel); 52 | } 53 | 54 | File tempEPGFileRound = File.createTempFile("epg", "xml"); 55 | FileOutputStream fos = new FileOutputStream(tempEPGFileRound); 56 | IOUtils.copy(channel, fos); 57 | fos.flush(); 58 | channel.close(); 59 | fos.close(); 60 | connection.disconnect(); 61 | 62 | if (tempEPGFile != null && tempEPGFile.exists()) { 63 | tempEPGFile.delete(); 64 | } 65 | tempEPGFile = tempEPGFileRound; 66 | lastUpdate=new Date().getTime(); 67 | } 68 | } 69 | 70 | 71 | public File getEPG() throws IOException { 72 | return tempEPGFile; 73 | } 74 | 75 | private HttpURLConnection getURLConnection(String url) throws MalformedURLException, IOException { 76 | URL tvheadendURL = new URL(url); 77 | HttpURLConnection connection = (HttpURLConnection) tvheadendURL.openConnection(); 78 | connection.setReadTimeout(10000); 79 | if (tvheadendURL.getUserInfo() != null) { 80 | String basicAuth = "Basic " + new String(Base64.getEncoder().encode(tvheadendURL.getUserInfo().getBytes())); 81 | connection.setRequestProperty("Authorization", basicAuth); 82 | } 83 | connection.setRequestMethod("GET"); 84 | connection.connect(); 85 | return connection; 86 | } 87 | 88 | public List getEPGFromChannelID(String epgID) { 89 | List epgPrograms = new ArrayList<>(); 90 | return epgPrograms; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/GeoIPService.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 4 | import com.maxmind.db.CHMCache; 5 | import com.maxmind.geoip2.DatabaseReader; 6 | import jakarta.annotation.PostConstruct; 7 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 8 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.scheduling.annotation.Scheduled; 12 | import org.springframework.stereotype.Service; 13 | 14 | 15 | import java.io.*; 16 | import java.net.HttpURLConnection; 17 | import java.net.MalformedURLException; 18 | import java.net.URL; 19 | import java.util.zip.GZIPInputStream; 20 | 21 | @Service 22 | public class GeoIPService { 23 | private final Logger logger = LoggerFactory.getLogger(GeoIPService.class); 24 | private File tmpGEOIPFile; 25 | private DatabaseReader geoIPDB; 26 | private final ProxyLiveConfiguration config; 27 | 28 | public GeoIPService(ProxyLiveConfiguration config) { 29 | this.config = config; 30 | } 31 | 32 | public boolean isServiceEnabled(){ 33 | return config.getGeoIP().isEnabled(); 34 | } 35 | 36 | @Scheduled(fixedDelay = 86400 * 1000) //Every 24H 37 | @PostConstruct 38 | private void downloadIPLocationDatabase() throws Exception { 39 | if(config.getGeoIP().isEnabled()){ 40 | logger.info("Downloading GEOIP Database from: "+config.getGeoIP().getUrl()); 41 | 42 | File tmpGEOIPFileRound = File.createTempFile("geoIP", "mmdb"); 43 | FileOutputStream fos = new FileOutputStream(tmpGEOIPFileRound); 44 | HttpURLConnection connection = getURLConnection(config.getGeoIP().getUrl()); 45 | if (connection.getResponseCode() != 200) { 46 | return; 47 | } 48 | TarArchiveInputStream tarGzGeoIPStream = new TarArchiveInputStream(new GZIPInputStream(connection.getInputStream())); 49 | TarArchiveEntry entry= null; 50 | int offset; 51 | long pointer=0; 52 | while ((entry = tarGzGeoIPStream.getNextTarEntry()) != null) { 53 | pointer+=entry.getSize(); 54 | if(entry.getName().endsWith("GeoLite2-City.mmdb")){ 55 | byte[] content = new byte[(int) entry.getSize()]; 56 | offset=0; 57 | //FileInputStream fis = new FileInputStream(entry.getFile()); 58 | //IOUtils.copy(fis,fos); 59 | //tarGzGeoIPStream.skip(pointer); 60 | //tarGzGeoIPStream.read(content,offset,content.length-offset); 61 | //IOUtils.write(content,fos); 62 | int r; 63 | byte[] b = new byte[1024]; 64 | while ((r = tarGzGeoIPStream.read(b)) != -1) { 65 | fos.write(b, 0, r); 66 | } 67 | //fis.close(); 68 | break; 69 | } 70 | } 71 | tarGzGeoIPStream.close(); 72 | fos.flush(); 73 | fos.close(); 74 | connection.disconnect(); 75 | geoIPDB = new DatabaseReader.Builder(tmpGEOIPFileRound).withCache(new CHMCache()).build(); 76 | if (tmpGEOIPFile != null && tmpGEOIPFile.exists()) { 77 | tmpGEOIPFile.delete(); 78 | } 79 | tmpGEOIPFile = tmpGEOIPFileRound; 80 | } 81 | } 82 | 83 | public DatabaseReader geoGEOInfoReader(){ 84 | return geoIPDB; 85 | } 86 | 87 | private HttpURLConnection getURLConnection(String urlString) throws MalformedURLException, IOException { 88 | URL url = new URL(urlString); 89 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 90 | connection.setReadTimeout(10000); 91 | connection.setRequestMethod("GET"); 92 | connection.connect(); 93 | return connection; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/LDAPAuthenticationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.service; 7 | 8 | import com.github.segator.proxylive.config.LDAPAutentication; 9 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 10 | import com.github.segator.proxylive.helper.AuthorityRoles; 11 | import jakarta.annotation.PostConstruct; 12 | import org.json.simple.parser.ParseException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.security.core.GrantedAuthority; 16 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 17 | 18 | 19 | import javax.naming.Context; 20 | import javax.naming.NamingEnumeration; 21 | import javax.naming.NamingException; 22 | import javax.naming.directory.DirContext; 23 | import javax.naming.directory.SearchControls; 24 | import javax.naming.directory.SearchResult; 25 | import javax.naming.ldap.InitialLdapContext; 26 | import javax.naming.ldap.LdapContext; 27 | import java.io.IOException; 28 | import java.net.MalformedURLException; 29 | import java.net.ProtocolException; 30 | import java.util.ArrayList; 31 | import java.util.Hashtable; 32 | import java.util.List; 33 | 34 | /** 35 | * 36 | * @author isaac 37 | */ 38 | public class LDAPAuthenticationService implements AuthenticationService { 39 | private final Logger logger = LoggerFactory.getLogger(LDAPAuthenticationService.class); 40 | private LDAPAutentication ldapAuthConfig; 41 | private final ProxyLiveConfiguration configuration; 42 | 43 | public LDAPAuthenticationService(ProxyLiveConfiguration configuration) { 44 | this.configuration = configuration; 45 | } 46 | 47 | @Override 48 | public boolean loginUser(String user, String password) throws Exception { 49 | throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. 50 | } 51 | 52 | @Override 53 | public List getUserRoles(String user) { 54 | ArrayList roles = new ArrayList(); 55 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.USER.getAuthority())); 56 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.ALLOW_ENCODING.getAuthority())); 57 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.ADMIN.getAuthority())); 58 | return roles; 59 | } 60 | 61 | @PostConstruct 62 | private void initialize() throws MalformedURLException, ProtocolException, IOException, ParseException, NamingException { 63 | ldapAuthConfig = configuration.getAuthentication().getLdap(); 64 | Hashtable env = new Hashtable(); 65 | env.put(Context.SECURITY_AUTHENTICATION, "simple"); 66 | env.put(Context.SECURITY_PRINCIPAL, ldapAuthConfig.getUser()); 67 | env.put(Context.SECURITY_CREDENTIALS, ldapAuthConfig.getPassword()); 68 | env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 69 | env.put(Context.PROVIDER_URL, "ldap://"+ldapAuthConfig.getServer()+"/"+ldapAuthConfig.getSearchBase()); 70 | env.put("java.naming.ldap.attributes.binary", "objectSID"); 71 | LdapContext ctx = new InitialLdapContext(); 72 | SearchResult srLdapUser = findAccountByAccountName(ctx, ldapAuthConfig.getSearchBase(), "segator"); 73 | String primaryGroupSID = getPrimaryGroupSID(srLdapUser); 74 | 75 | 76 | //3) get the users Primary Group 77 | String primaryGroupName = findGroupBySID(ctx, ldapAuthConfig.getSearchBase(), primaryGroupSID); 78 | logger.trace(primaryGroupName); 79 | 80 | } 81 | 82 | public SearchResult findAccountByAccountName(DirContext ctx, String ldapSearchBase, String accountName) throws NamingException { 83 | 84 | String searchFilter = "(&(objectClass=user)(sAMAccountName=" + accountName + "))"; 85 | 86 | SearchControls searchControls = new SearchControls(); 87 | searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); 88 | 89 | NamingEnumeration results = ctx.search(ldapSearchBase, searchFilter, searchControls); 90 | 91 | SearchResult searchResult = null; 92 | if (results.hasMoreElements()) { 93 | searchResult = (SearchResult) results.nextElement(); 94 | 95 | //make sure there is not another item available, there should be only 1 match 96 | if (results.hasMoreElements()) { 97 | logger.warn("Matched multiple users for the accountName: " + accountName); 98 | return null; 99 | } 100 | } 101 | 102 | return searchResult; 103 | } 104 | 105 | public String getPrimaryGroupSID(SearchResult srLdapUser) throws NamingException { 106 | byte[] objectSID = (byte[]) srLdapUser.getAttributes().get("objectSid").get(); 107 | String strPrimaryGroupID = (String) srLdapUser.getAttributes().get("primaryGroupID").get(); 108 | 109 | String strObjectSid = decodeSID(objectSID); 110 | 111 | return strObjectSid.substring(0, strObjectSid.lastIndexOf('-') + 1) + strPrimaryGroupID; 112 | } 113 | 114 | public String findGroupBySID(DirContext ctx, String ldapSearchBase, String sid) throws NamingException { 115 | 116 | String searchFilter = "(&(objectClass=group)(objectSid=" + sid + "))"; 117 | 118 | SearchControls searchControls = new SearchControls(); 119 | searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); 120 | 121 | NamingEnumeration results = ctx.search(ldapSearchBase, searchFilter, searchControls); 122 | 123 | if (results.hasMoreElements()) { 124 | SearchResult searchResult = (SearchResult) results.nextElement(); 125 | 126 | //make sure there is not another item available, there should be only 1 match 127 | if (results.hasMoreElements()) { 128 | logger.warn("Matched multiple groups for the group with SID: " + sid); 129 | return null; 130 | } else { 131 | return (String) searchResult.getAttributes().get("sAMAccountName").get(); 132 | } 133 | } 134 | return null; 135 | } 136 | 137 | public static String decodeSID(byte[] sid) { 138 | 139 | final StringBuilder strSid = new StringBuilder("S-"); 140 | 141 | // get version 142 | final int revision = sid[0]; 143 | strSid.append(Integer.toString(revision)); 144 | 145 | //next byte is the count of sub-authorities 146 | final int countSubAuths = sid[1] & 0xFF; 147 | 148 | //get the authority 149 | long authority = 0; 150 | //String rid = ""; 151 | for (int i = 2; i <= 7; i++) { 152 | authority |= ((long) sid[i]) << (8 * (5 - (i - 2))); 153 | } 154 | strSid.append("-"); 155 | strSid.append(Long.toHexString(authority)); 156 | 157 | //iterate all the sub-auths 158 | int offset = 8; 159 | int size = 4; //4 bytes for each sub auth 160 | for (int j = 0; j < countSubAuths; j++) { 161 | long subAuthority = 0; 162 | for (int k = 0; k < size; k++) { 163 | subAuthority |= (long) (sid[offset + k] & 0xFF) << (8 * k); 164 | } 165 | 166 | strSid.append("-"); 167 | strSid.append(subAuthority); 168 | 169 | offset += size; 170 | } 171 | 172 | return strSid.toString(); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/PlexAuthenticationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.service; 7 | 8 | import com.github.segator.proxylive.config.PlexAuthentication; 9 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 10 | import com.github.segator.proxylive.helper.AuthorityRoles; 11 | import jakarta.annotation.PostConstruct; 12 | import org.json.simple.JSONObject; 13 | import org.json.simple.parser.JSONParser; 14 | import org.json.simple.parser.ParseException; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.scheduling.annotation.Scheduled; 18 | import org.springframework.security.core.GrantedAuthority; 19 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 20 | import org.w3c.dom.Document; 21 | import org.w3c.dom.Element; 22 | import org.w3c.dom.NodeList; 23 | import org.xml.sax.InputSource; 24 | import org.xml.sax.SAXException; 25 | 26 | import javax.xml.parsers.DocumentBuilder; 27 | import javax.xml.parsers.DocumentBuilderFactory; 28 | import javax.xml.parsers.ParserConfigurationException; 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.io.InputStreamReader; 32 | import java.net.*; 33 | import java.util.ArrayList; 34 | import java.util.Base64; 35 | import java.util.Date; 36 | import java.util.List; 37 | 38 | /** 39 | * 40 | * @author isaac 41 | */ 42 | 43 | public class PlexAuthenticationService implements AuthenticationService { 44 | Logger logger = LoggerFactory.getLogger(PlexAuthenticationService.class); 45 | private long lastUpdate=0; 46 | private List allowedUsers; 47 | private final ProxyLiveConfiguration configuration; 48 | 49 | public PlexAuthenticationService(ProxyLiveConfiguration configuration) { 50 | this.configuration = configuration; 51 | } 52 | 53 | @Override 54 | public boolean loginUser(String user, String password) throws IOException, ParseException { 55 | if(user!=null && allowedUsers.contains(user.toLowerCase())){ 56 | //Check user pass is valid 57 | return getUserData(user, password)!=null; 58 | } 59 | return false; 60 | } 61 | 62 | @Scheduled(fixedDelay = 30 * 1000)//Every 30 seconds 63 | public void refreshPlexUsers() throws IOException { 64 | PlexAuthentication plexAuthConfig = configuration.getAuthentication().getPlex(); 65 | if(new Date().getTime()-lastUpdate>+(plexAuthConfig.getRefresh()*1000)) { 66 | List allowedUsers = new ArrayList(); 67 | allowedUsers.add(plexAuthConfig.getAdminUser()); 68 | URL url = new URL(String.format("https://%s:%s@plex.tv/api/users", URLEncoder.encode(plexAuthConfig.getAdminUser(), "UTF-8"), URLEncoder.encode(plexAuthConfig.getAdminPass(), "UTF-8"))); 69 | HttpURLConnection connection = createConnection(url); 70 | connection.connect(); 71 | if (connection.getResponseCode() != 200) { 72 | throw new IOException("unexpected error when getting users list:" + connection.getResponseCode()); 73 | } 74 | Document dom = newDocumentFromInputStream(connection.getInputStream()); 75 | NodeList users = dom.getElementsByTagName("User"); 76 | for (int i = 0; i < users.getLength(); i++) { 77 | Element userEl = (Element) users.item(i); 78 | NodeList servers = userEl.getElementsByTagName("Server"); 79 | if (servers.getLength() > 0) { 80 | for (int j = 0; j < servers.getLength(); j++) { 81 | Element server = (Element) servers.item(j); 82 | if (server.getAttribute("name").equals(plexAuthConfig.getServerName())) { 83 | allowedUsers.add(userEl.getAttribute("username").toLowerCase()); 84 | } 85 | } 86 | } 87 | } 88 | this.lastUpdate=new Date().getTime(); 89 | this.allowedUsers = allowedUsers; 90 | } 91 | } 92 | 93 | @Override 94 | public List getUserRoles(String user) { 95 | ArrayList roles = new ArrayList(); 96 | if(allowedUsers.contains(user.toLowerCase())){ 97 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.USER.getAuthority())); 98 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.ALLOW_ENCODING.getAuthority())); 99 | } 100 | if(user.equalsIgnoreCase(configuration.getAuthentication().getPlex().getAdminUser())){ 101 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.ADMIN.getAuthority())); 102 | } 103 | return roles; 104 | } 105 | 106 | private JSONObject getUserData(String user, String pass) throws MalformedURLException, IOException, ParseException { 107 | URL url = new URL(String.format("https://%s:%s@plex.tv/users/sign_in.json", URLEncoder.encode(user,"UTF-8"), URLEncoder.encode(pass,"UTF-8"))); 108 | HttpURLConnection connection = createConnection(url); 109 | connection.setRequestProperty("X-Plex-Client-Identifier", "proxylive"); 110 | connection.setRequestMethod("POST"); 111 | connection.connect(); 112 | if (connection.getResponseCode() != 201) { 113 | return null; 114 | } 115 | JSONParser jsonParser = new JSONParser(); 116 | JSONObject response = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream(), "UTF-8")); 117 | return (JSONObject) response.get("user"); 118 | } 119 | 120 | @PostConstruct 121 | private void initialize() throws MalformedURLException, ProtocolException, IOException, ParseException { 122 | PlexAuthentication plexAuthConfig = configuration.getAuthentication().getPlex(); 123 | refreshPlexUsers(); 124 | } 125 | 126 | private HttpURLConnection createConnection(URL url) throws ProtocolException, IOException { 127 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 128 | 129 | connection.setReadTimeout(10000); 130 | if (url.getUserInfo() != null) { 131 | String basicAuth = "Basic " + new String(Base64.getEncoder().encode(URLDecoder.decode(url.getUserInfo(),"UTF-8").getBytes())); 132 | connection.setRequestProperty("Authorization", basicAuth); 133 | } 134 | connection.setRequestMethod("GET"); 135 | connection.setReadTimeout(10000); 136 | return connection; 137 | } 138 | 139 | public Document newDocumentFromInputStream(InputStream in) { 140 | DocumentBuilderFactory factory = null; 141 | DocumentBuilder builder = null; 142 | Document ret = null; 143 | 144 | try { 145 | factory = DocumentBuilderFactory.newInstance(); 146 | builder = factory.newDocumentBuilder(); 147 | } catch (ParserConfigurationException e) { 148 | logger.error("Error",e); 149 | } 150 | 151 | try { 152 | ret = builder.parse(new InputSource(in)); 153 | } catch (SAXException e) { 154 | logger.error("Error",e); 155 | } catch (IOException e) { 156 | logger.error("Error",e); 157 | } 158 | return ret; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/PrometheusMetrics.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | 4 | import com.github.segator.proxylive.ProxyLiveUtils; 5 | import com.github.segator.proxylive.entity.ClientInfo; 6 | import com.github.segator.proxylive.processor.IStreamProcessor; 7 | import com.github.segator.proxylive.tasks.DirectTranscodeTask; 8 | import com.github.segator.proxylive.tasks.IStreamTask; 9 | import com.github.segator.proxylive.tasks.ProcessorTasks; 10 | import com.github.segator.proxylive.tasks.StreamProcessorsSession; 11 | import io.micrometer.core.instrument.*; 12 | import io.micrometer.core.instrument.binder.MeterBinder; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.scheduling.annotation.Scheduled; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.Date; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | @Component 24 | public class PrometheusMetrics implements MeterBinder { 25 | 26 | private final StreamProcessorsSession streamProcessorsSession; 27 | private final ProcessorTasks tasksProcessor; 28 | private MeterRegistry meterRegistry; 29 | private List activeCustomMetrics; 30 | 31 | public PrometheusMetrics(StreamProcessorsSession streamProcessorsSession, ProcessorTasks tasksProcessor) { 32 | this.streamProcessorsSession = streamProcessorsSession; 33 | this.tasksProcessor = tasksProcessor; 34 | } 35 | 36 | @Override 37 | public void bindTo(MeterRegistry meterRegistry) { 38 | this.meterRegistry=meterRegistry; 39 | this.activeCustomMetrics=new ArrayList(); 40 | } 41 | 42 | 43 | 44 | @Scheduled(fixedDelay = 5 * 1000) //Every 5s 45 | private void refreshMetrics(){ 46 | for (Meter meter: activeCustomMetrics) { 47 | meterRegistry.remove(meter); 48 | } 49 | List listOfMetrics=new ArrayList(); 50 | for (ClientInfo clientInfo: new ArrayList<>(streamProcessorsSession.getClientInfoList())) { 51 | List tagsClientInfo = new ArrayList(); 52 | tagsClientInfo.add(Tag.of("user",clientInfo.getClientUser())); 53 | tagsClientInfo.add(Tag.of("ip",clientInfo.getIp().getHostAddress())); 54 | tagsClientInfo.add(Tag.of("user-agent",clientInfo.getBrowserInfo())); 55 | 56 | if(clientInfo.getGeoInfo()!=null && clientInfo.getGeoInfo().getCity()!=null && clientInfo.getGeoInfo().getCity().getName()!=null) { 57 | tagsClientInfo.add(Tag.of("ip-city", clientInfo.getGeoInfo().getCity().getName())); 58 | }else{ 59 | tagsClientInfo.add(Tag.of("ip-city", "Not found")); 60 | } 61 | if(clientInfo.getGeoInfo()!=null && clientInfo.getGeoInfo().getCountry()!=null && clientInfo.getGeoInfo().getCountry().getName()!=null) { 62 | tagsClientInfo.add(Tag.of("ip-country", clientInfo.getGeoInfo().getCountry().getName())); 63 | }else{ 64 | tagsClientInfo.add(Tag.of("ip-country", "Not found")); 65 | } 66 | if(clientInfo.getGeoInfo()!=null && clientInfo.getGeoInfo().getLocation()!=null) { 67 | tagsClientInfo.add(Tag.of("ip-latitude", clientInfo.getGeoInfo().getLocation().getLatitude().toString())); 68 | tagsClientInfo.add(Tag.of("ip-longitude", clientInfo.getGeoInfo().getLocation().getLongitude().toString())); 69 | }else{ 70 | tagsClientInfo.add(Tag.of("ip-latitude", "0")); 71 | tagsClientInfo.add(Tag.of("ip-longitude", "0")); 72 | } 73 | 74 | 75 | for (IStreamProcessor stream: new ArrayList<>(clientInfo.getStreams())) { 76 | if(stream.getTask()!=null && stream.getTask().getIdentifier()!=null) { 77 | List tagsClientStream = new ArrayList(tagsClientInfo); 78 | tagsClientStream.add(Tag.of("task_identifier", stream.getTask().getIdentifier())); 79 | listOfMetrics.add(Gauge.builder("client_stream_info", this, value -> 0).description("task status").tags(tagsClientStream).baseUnit("unit").register(meterRegistry)); 80 | } 81 | } 82 | } 83 | Collection tasks = tasksProcessor.getOperationMap().values(); 84 | for (IStreamTask task: tasks) { 85 | //task_status 86 | String profile="raw"; 87 | if(task instanceof DirectTranscodeTask){ 88 | DirectTranscodeTask transcodeTask = (DirectTranscodeTask) task; 89 | profile = transcodeTask.getProfile(); 90 | } 91 | List tagsStatus = new ArrayList(); 92 | tagsStatus.add(Tag.of("source",task.getSource())); 93 | tagsStatus.add(Tag.of("identifier",task.getIdentifier())); 94 | tagsStatus.add(Tag.of("profile",profile)); 95 | listOfMetrics.add(Gauge.builder("task_status",this, value -> task.isCrashed()?1:0).description("task status").tags(tagsStatus).baseUnit("status").register(meterRegistry)); 96 | 97 | //task running_time 98 | long now = new Date().getTime(); 99 | Date runDate = task.startTaskDate(); 100 | if (runDate != null) { 101 | List tagsRunningTime = new ArrayList(); 102 | tagsRunningTime.add(Tag.of("identifier",task.getIdentifier())); 103 | long runTime = now - task.startTaskDate().getTime(); 104 | listOfMetrics.add(Gauge.builder("task_running_time",this, value -> runTime).description("task running time").tags(tagsRunningTime).baseUnit("seconds").register(meterRegistry)); 105 | } 106 | } 107 | 108 | /*List notMatch = activeCustomMetrics.stream() 109 | .filter(ch -> listOfMetrics.stream().noneMatch(other -> ch.getId().equals(other.getId()))) 110 | .collect(Collectors.toList()); 111 | for (Meter meter: notMatch) { 112 | meterRegistry.remove(meter); 113 | }*/ 114 | activeCustomMetrics = listOfMetrics; 115 | //Tod@ 116 | //treure la llista de IDS actuals i borra els IDS que no estan a la llista listOfMetricsID amb meterRegistry.remove() 117 | //meterRegistry.getMeters() 118 | 119 | //Gauge.builder("task_status",this,value -> 4).description("desc").tag("tag","asd").baseUnit("unit").register(meterRegistry); 120 | //Gauge.builder("task_running_time",this,value -> 4).description("desc").tag("tag","asd").baseUnit("unit").register(meterRegistry); 121 | } 122 | 123 | 124 | } -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.service; 2 | 3 | import com.github.segator.proxylive.helper.AuthorityRoles; 4 | import com.github.segator.proxylive.helper.JwtHelper; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.AuthorityUtils; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.time.Instant; 10 | import java.util.Calendar; 11 | import java.util.List; 12 | 13 | @Service 14 | public class TokenService { 15 | private final JwtHelper jwtHelper; 16 | 17 | public TokenService(JwtHelper jwtHelper) { 18 | this.jwtHelper = jwtHelper; 19 | } 20 | 21 | public String createServiceAccountRequestToken(String subject){ 22 | List grantedAuthorities = AuthorityUtils 23 | .createAuthorityList( 24 | AuthorityRoles.SERVICE_ACCOUNT.getAuthority(), 25 | AuthorityRoles.ALLOW_ENCODING.getAuthority() 26 | ); 27 | Calendar calendar = Calendar.getInstance(); 28 | calendar.setTimeInMillis(Instant.now().toEpochMilli()); 29 | calendar.add(Calendar.MINUTE, 15); 30 | return jwtHelper.createJwtForClaims(String.format("SA-%s",subject),grantedAuthorities,calendar.getTime()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/service/WithoutAuthenticationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.github.segator.proxylive.service; 7 | 8 | import com.github.segator.proxylive.helper.AuthorityRoles; 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * 17 | * @author isaac 18 | */ 19 | public class WithoutAuthenticationService implements AuthenticationService { 20 | 21 | @Override 22 | public boolean loginUser(String user, String password) throws Exception { 23 | return true; 24 | } 25 | 26 | @Override 27 | public List getUserRoles(String user) { 28 | ArrayList roles = new ArrayList(); 29 | 30 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.USER.getAuthority())); 31 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.ADMIN.getAuthority())); 32 | roles.add(new SimpleGrantedAuthority(AuthorityRoles.ALLOW_ENCODING.getAuthority())); 33 | return roles; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/BroadcastCircularBufferedOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.stream; 25 | 26 | import java.io.IOException; 27 | import java.io.OutputStream; 28 | import java.nio.ByteBuffer; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | /** 33 | * 34 | * @author Isaac Aymerich 35 | */ 36 | public class BroadcastCircularBufferedOutputStream extends OutputStream { 37 | 38 | private final ByteBuffer byteBuffer; 39 | private boolean filled; 40 | private final List clientsList; 41 | 42 | 43 | public BroadcastCircularBufferedOutputStream(int size) { 44 | filled = false; 45 | clientsList = new ArrayList(); 46 | byteBuffer = ByteBuffer.allocateDirect(size); 47 | 48 | } 49 | 50 | public synchronized List getClientsList() { 51 | return clientsList; 52 | } 53 | 54 | public synchronized boolean isFilled() { 55 | return filled; 56 | } 57 | 58 | public synchronized boolean removeClientConsumer(ClientBroadcastedInputStream is) { 59 | return clientsList.remove(is); 60 | } 61 | 62 | public synchronized void removeAllConsumers() { 63 | clientsList.clear(); 64 | } 65 | 66 | @Override 67 | public synchronized void write(int b) throws IOException { 68 | byteBuffer.put((byte)(b & 0xFF)); 69 | if (byteBuffer.position() == byteBuffer.limit()) { 70 | filled = true; 71 | byteBuffer.rewind(); 72 | } 73 | } 74 | 75 | @Override 76 | public void write(byte b[], int off, int len) throws IOException { 77 | if (byteBuffer.position() + len > byteBuffer.limit()) { 78 | int writeLen = byteBuffer.limit() - byteBuffer.position(); 79 | byteBuffer.put(b, off, writeLen); 80 | filled=true; 81 | byteBuffer.rewind(); 82 | byteBuffer.put(b, writeLen, len - writeLen); 83 | } else { 84 | byteBuffer.put(b, off, len); 85 | } 86 | } 87 | 88 | public synchronized ClientBroadcastedInputStream getConsumer(String id) { 89 | ClientBroadcastedInputStream clientInputStream = new ClientBroadcastedInputStream(byteBuffer,id); 90 | clientsList.add(clientInputStream); 91 | return clientInputStream; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/ClientBroadcastedInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.stream; 25 | 26 | import com.github.segator.proxylive.service.GeoIPService; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.nio.ByteBuffer; 33 | import java.nio.channels.InterruptedByTimeoutException; 34 | 35 | /** 36 | * 37 | * @author Isaac Aymerich 38 | */ 39 | public class ClientBroadcastedInputStream extends InputStream { 40 | private final Logger logger = LoggerFactory.getLogger(ClientBroadcastedInputStream.class); 41 | private final ByteBuffer sourceByteBuffer, clientByteBuffer; 42 | private final String id; 43 | 44 | ClientBroadcastedInputStream(ByteBuffer byteBuffer, String id) { 45 | this.sourceByteBuffer = byteBuffer; 46 | this.clientByteBuffer = byteBuffer.asReadOnlyBuffer(); 47 | this.id = id; 48 | 49 | } 50 | 51 | @Override 52 | public int available() throws IOException { 53 | int available = 0; 54 | if (clientByteBuffer.position() > sourceByteBuffer.position()) { 55 | available = sourceByteBuffer.limit() - clientByteBuffer.position() + sourceByteBuffer.position(); 56 | } else { 57 | available = sourceByteBuffer.position() - clientByteBuffer.position(); 58 | } 59 | return available; 60 | } 61 | 62 | @Override 63 | public int read() throws IOException { 64 | int readed = -1; 65 | if (clientByteBuffer.position() == sourceByteBuffer.position()) { 66 | try { 67 | while (available() == 0) { 68 | Thread.sleep(10); 69 | } 70 | } catch (InterruptedException ex) { 71 | throw new IOException("Interrupted waiting"); 72 | } 73 | } 74 | readed = clientByteBuffer.get(); 75 | if (clientByteBuffer.position() == sourceByteBuffer.limit()) { 76 | logger.trace("jump" + id); 77 | clientByteBuffer.rewind(); 78 | } 79 | return readed & 0xFF; 80 | } 81 | 82 | @Override 83 | public int read(byte b[]) throws IOException { 84 | int readed = -1; 85 | int sourcePosition = sourceByteBuffer.position(); 86 | if (clientByteBuffer.position() > sourcePosition) { 87 | readed = b.length; 88 | if (b.length > sourceByteBuffer.limit() - clientByteBuffer.position()) { 89 | readed = sourceByteBuffer.limit() - clientByteBuffer.position(); 90 | clientByteBuffer.get(b, 0, readed); 91 | clientByteBuffer.rewind(); 92 | } else { 93 | clientByteBuffer.get(b, 0, readed); 94 | } 95 | } else if(clientByteBuffer.position() == sourceByteBuffer.position()){ 96 | return 0; 97 | } else if (clientByteBuffer.position() <= sourcePosition) { 98 | //Write buffer rewinded 99 | if (sourcePosition < clientByteBuffer.position()) { 100 | readed = read(b); 101 | } else { 102 | readed = sourcePosition - clientByteBuffer.position(); 103 | if (b.length < readed) { 104 | readed = b.length; 105 | } 106 | clientByteBuffer.get(b, 0, readed); 107 | } 108 | } 109 | 110 | return readed; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/FFmpegInputStream.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.stream; 2 | 3 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 4 | import com.github.segator.proxylive.entity.Channel; 5 | import org.apache.commons.exec.CommandLine; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | 15 | public class FFmpegInputStream extends VideoInputStream { 16 | 17 | private String url; 18 | private Process process; 19 | private Thread threadErrorStream; 20 | private ProxyLiveConfiguration config; 21 | private InputStream ffmpegInputStream; 22 | private Channel channel; 23 | private boolean alive; 24 | public FFmpegInputStream(String url, Channel channel, ProxyLiveConfiguration config){ 25 | this.url= url; 26 | this.channel= channel; 27 | this.config=config; 28 | } 29 | 30 | 31 | @Override 32 | public boolean isConnected() { 33 | return process.isAlive(); 34 | } 35 | 36 | @Override 37 | public boolean connect() throws IOException { 38 | alive=true; 39 | String encryptionParams = ""; 40 | String defaultVCodec = "copy"; 41 | if(channel.getEncryptionKey()!=null){ 42 | encryptionParams = " -re -timeout 5 -cenc_decryption_key " + channel.getEncryptionKey() + " "; // -fflags +genpts -async 1 -rtbufsize 2000M -probesize 1000000 -analyzeduration 1000000 43 | } 44 | StringBuilder inputHeaders = new StringBuilder(); 45 | String userAgent = ""; 46 | for (Map.Entry entry :channel.getSourceHeaders().entrySet()) { 47 | if(entry.getKey().contains("user-agent")){ 48 | userAgent = "-user_agent \"" + entry.getValue() + "\""; 49 | continue; 50 | } 51 | inputHeaders.append(String.format(" -headers %s:%s", entry.getKey().replace("http-",""), entry.getValue())); 52 | } 53 | String ffmpegCommand = config.getFfmpeg().getPath() + " " + encryptionParams + userAgent + inputHeaders + " -i " +url + " " + (channel.getFfmpegParameters()!=null?channel.getFfmpegParameters():"") + " -codec " +defaultVCodec + " " + config.getFfmpeg().getMpegTS().getParameters() + " -"; 54 | var cli = CommandLine.parse(ffmpegCommand); 55 | List commandList = new ArrayList<>(); 56 | commandList.add(config.getFfmpeg().getPath()); 57 | commandList.addAll(Arrays.asList(cli.getArguments())); 58 | process = new ProcessBuilder( commandList).start(); 59 | System.out.println(ffmpegCommand); 60 | ffmpegInputStream = process.getInputStream(); 61 | threadErrorStream = printErrStream(process.getErrorStream(),process); 62 | return true; 63 | } 64 | 65 | @Override 66 | public int read() throws IOException { 67 | return ffmpegInputStream.read(); 68 | } 69 | 70 | @Override 71 | public int read(byte b[]) throws IOException { 72 | return ffmpegInputStream.read(b); 73 | } 74 | 75 | public void close() throws IOException { 76 | if (isConnected()) { 77 | try { 78 | if(process.isAlive()) { 79 | process.destroy(); 80 | } 81 | }catch(Exception ex){} 82 | try{ 83 | if(threadErrorStream.isAlive()) { 84 | threadErrorStream.join(); 85 | } 86 | }catch(Exception ex){} 87 | } else { 88 | throw new IOException("The Stream of " + url + " is not connected"); 89 | } 90 | } 91 | 92 | private Thread printErrStream(InputStream is, Process proc) { 93 | Thread t = new Thread(new Runnable() { 94 | @Override 95 | public void run() { 96 | byte[] buffer = new byte[1024]; 97 | try { 98 | while(proc.isAlive()){ 99 | int r = is.read(buffer); 100 | if(r > 0 ) { 101 | System.out.print(new String(buffer, 0, r)); 102 | } 103 | Thread.sleep(10); 104 | } 105 | } catch (Exception e) { 106 | e.printStackTrace(); 107 | } 108 | } 109 | }); 110 | t.start(); 111 | return t; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/ProcessInputStream.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.stream; 2 | 3 | import com.github.segator.proxylive.ProxyLiveUtils; 4 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 5 | import com.github.segator.proxylive.entity.Channel; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.util.Date; 14 | 15 | public class ProcessInputStream extends VideoInputStream { 16 | Logger logger = LoggerFactory.getLogger(ProcessInputStream.class); 17 | private String url; 18 | private Process process; 19 | private Thread threadErrorStream; 20 | private InputStream ffmpegInputStream; 21 | private boolean terminate; 22 | 23 | public ProcessInputStream(String url){ 24 | this.url= url; 25 | this.terminate=false; 26 | } 27 | 28 | 29 | @Override 30 | public boolean isConnected() { 31 | return process.isAlive(); 32 | } 33 | 34 | @Override 35 | public boolean connect() throws IOException { 36 | process = new ProcessBuilder().command(ProxyLiveUtils.translateCommandline(url)).start(); 37 | ffmpegInputStream = new WithoutBlockingInputStream(process.getInputStream()); 38 | threadErrorStream = errorStreamThread(new WithoutBlockingInputStream(process.getErrorStream()),process); 39 | return true; 40 | } 41 | 42 | @Override 43 | public int read() throws IOException { 44 | return ffmpegInputStream.read(); 45 | } 46 | 47 | @Override 48 | public int read(byte b[]) throws IOException { 49 | return ffmpegInputStream.read(b); 50 | } 51 | 52 | public void close() throws IOException { 53 | if (isConnected()) { 54 | terminate = true; 55 | ffmpegInputStream.close(); 56 | try { 57 | if(process.isAlive()) { 58 | process.destroy(); 59 | } 60 | }catch(Exception ex){} 61 | try{ 62 | if(threadErrorStream.isAlive()) { 63 | threadErrorStream.join(); 64 | } 65 | }catch(Exception ex){} 66 | } else { 67 | throw new IOException("The Stream of " + url + " is not connected"); 68 | } 69 | } 70 | 71 | private Thread errorStreamThread(InputStream is,Process proc) { 72 | Thread t = new Thread(new Runnable() { 73 | @Override 74 | public void run() { 75 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); 76 | try { 77 | while(proc.isAlive() && !terminate){ 78 | if(br.ready()) { 79 | try { 80 | logger.debug("[" + url + "] " + br.readLine()); 81 | }catch(Exception e){ 82 | //if the buffer it's empty after readiness it crash with underlying input stream returned zero bytes 83 | } 84 | }else{ 85 | Thread.sleep(200); 86 | } 87 | } 88 | br.close(); 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | }); 94 | t.start(); 95 | return t; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/UDPInputStream.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.stream; 2 | 3 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 4 | import com.github.segator.proxylive.service.EPGService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.IOException; 9 | import java.net.DatagramPacket; 10 | import java.net.DatagramSocket; 11 | import java.net.InetAddress; 12 | import java.net.MulticastSocket; 13 | import java.util.regex.Pattern; 14 | 15 | public class UDPInputStream extends VideoInputStream { 16 | private final Logger logger = LoggerFactory.getLogger(UDPInputStream.class); 17 | private final InetAddress server; 18 | private final int port; 19 | private final byte[] buffer; 20 | private byte[] currentPacket; 21 | private int currentPacketSize; 22 | private int currentPacketPosition=0; 23 | private ProxyLiveConfiguration config; 24 | private DatagramSocket clientSocket; 25 | 26 | 27 | public UDPInputStream(String url, ProxyLiveConfiguration config) throws IOException { 28 | this.config = config; 29 | url = url.split(Pattern.quote("udp://"))[1]; 30 | server = InetAddress.getByName(url.split(Pattern.quote(":"))[0]); 31 | port = Integer.parseInt(url.split(Pattern.quote(":"))[1]); 32 | buffer = new byte[131072]; 33 | } 34 | 35 | private void initializeConnection() throws IOException { 36 | //clientSocket = new DatagramSocket(); 37 | clientSocket = new MulticastSocket(port); 38 | ((MulticastSocket) clientSocket).joinGroup(server); 39 | clientSocket.setSoTimeout(config.getSource().getReconnectTimeout()*1000); 40 | //clientSocket.connect(server,port); 41 | } 42 | 43 | public boolean connect() throws IOException { 44 | initializeConnection(); 45 | return true; 46 | } 47 | 48 | public boolean isConnected() { 49 | return clientSocket.isBound(); 50 | } 51 | 52 | 53 | 54 | @Override 55 | public int read() throws IOException { 56 | return 0; 57 | } 58 | 59 | 60 | 61 | @Override 62 | public int read(byte b[]) throws IOException { 63 | if(currentPacket==null || currentPacketPosition==currentPacketSize) { 64 | DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length, server, port); 65 | clientSocket.receive(inPacket); 66 | currentPacket = inPacket.getData(); 67 | currentPacketSize = inPacket.getLength(); 68 | if(inPacket.getOffset()!=0){ 69 | logger.trace("offset !=0"); 70 | } 71 | currentPacketPosition=0; 72 | } 73 | int remainingBytes=currentPacketSize-currentPacketPosition; 74 | int toRead=b.length; 75 | if(b.length>remainingBytes){ 76 | toRead=remainingBytes; 77 | } 78 | int i=0; 79 | for (; i < toRead; i++) { 80 | b[i] = currentPacket[currentPacketPosition]; 81 | currentPacketPosition++; 82 | } 83 | return i; 84 | } 85 | 86 | public void close() throws IOException { 87 | if (isConnected()) { 88 | clientSocket.close(); 89 | } else { 90 | throw new IOException("The Stream of udp://" + server + ":"+port + " is not connected"); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/VideoInputStream.java: -------------------------------------------------------------------------------- 1 | package com.github.segator.proxylive.stream; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | public abstract class VideoInputStream extends InputStream { 7 | public abstract boolean isConnected(); 8 | public abstract boolean connect() throws IOException; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/WebInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.stream; 25 | 26 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.net.HttpURLConnection; 33 | import java.net.MalformedURLException; 34 | import java.net.URL; 35 | import java.util.Base64; 36 | 37 | /** 38 | * 39 | * @author Isaac Aymerich 40 | */ 41 | public class WebInputStream extends VideoInputStream{ 42 | private final Logger logger = LoggerFactory.getLogger(WebInputStream.class); 43 | private final URL url; 44 | private HttpURLConnection connection; 45 | private InputStream httpInputStream; 46 | private ProxyLiveConfiguration config; 47 | 48 | public WebInputStream(URL url, ProxyLiveConfiguration config) throws MalformedURLException, IOException { 49 | this.url = url; 50 | this.config = config; 51 | 52 | } 53 | 54 | private void initializeConnection() throws IOException { 55 | connection = (HttpURLConnection) url.openConnection(); 56 | connection.setRequestProperty("Connection","keep-alive"); 57 | connection.setRequestProperty("User-Agent",config.getUserAgent()); 58 | connection.setReadTimeout(config.getSource().getReconnectTimeout()*1000); 59 | if (url.getUserInfo() != null) { 60 | String basicAuth = "Basic " + new String(Base64.getEncoder().encode(url.getUserInfo().getBytes())); 61 | connection.setRequestProperty("Authorization", basicAuth); 62 | } 63 | connection.setRequestMethod("GET"); 64 | 65 | } 66 | 67 | public boolean connect() throws IOException { 68 | initializeConnection(); 69 | connection.connect(); 70 | boolean connected = connection.getResponseCode() == 200 || connection.getResponseCode() == 204; 71 | httpInputStream = new WithoutBlockingInputStream(connection.getInputStream()); 72 | return connected; 73 | } 74 | 75 | public boolean isConnected() { 76 | return httpInputStream != null; 77 | } 78 | 79 | @Override 80 | public int read() throws IOException { 81 | return httpInputStream.read(); 82 | } 83 | 84 | @Override 85 | public int read(byte b[]) throws IOException { 86 | return httpInputStream.read(b); 87 | } 88 | 89 | public void close() throws IOException { 90 | if (isConnected()) { 91 | httpInputStream.close(); 92 | } else { 93 | throw new IOException("The Stream of " + url.toString() + " is not connected"); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/stream/WithoutBlockingInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.stream; 25 | 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | 29 | /** 30 | * 31 | * @author Isaac Aymerich 32 | */ 33 | public class WithoutBlockingInputStream extends InputStream { 34 | 35 | private final InputStream is; 36 | private int available; 37 | 38 | public WithoutBlockingInputStream(InputStream is) { 39 | this.is = is; 40 | available = 0; 41 | } 42 | 43 | @Override 44 | public int read() throws IOException { 45 | return is.read(); 46 | } 47 | 48 | @Override 49 | public int read(byte b[], int off, int len) throws IOException { 50 | if (len > available) { 51 | available = is.available(); 52 | } 53 | if (available > 0) { 54 | int readed = is.read(b, off, len); 55 | available -= readed; 56 | return readed; 57 | } else { 58 | return 0; 59 | } 60 | } 61 | 62 | @Override 63 | public int available() throws IOException { 64 | return is.available(); 65 | } 66 | 67 | @Override 68 | public void close() throws IOException { 69 | is.close(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/tasks/IMultiplexerStreamer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.tasks; 25 | 26 | import com.github.segator.proxylive.stream.BroadcastCircularBufferedOutputStream; 27 | 28 | /** 29 | * 30 | * @author Isaac Aymerich 31 | */ 32 | public interface IMultiplexerStreamer extends IStreamTask { 33 | 34 | public BroadcastCircularBufferedOutputStream getMultiplexer(); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/tasks/IStreamTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.tasks; 25 | 26 | import com.github.segator.proxylive.processor.IStreamProcessor; 27 | import java.util.Date; 28 | 29 | /** 30 | * 31 | * @author Isaac Aymerich 32 | */ 33 | public interface IStreamTask extends Runnable { 34 | public String getSource(); 35 | 36 | public boolean isCrashed(); 37 | 38 | public void terminate(); 39 | 40 | public String getIdentifier(); 41 | 42 | public void initializate() throws Exception; 43 | 44 | public IStreamProcessor getSourceProcessor(); 45 | 46 | public Date startTaskDate(); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/tasks/RemoteTranscodeTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.tasks; 25 | 26 | import com.github.segator.proxylive.config.ProxyLiveConfiguration; 27 | import com.github.segator.proxylive.config.RemoteTranscoder; 28 | import com.github.segator.proxylive.processor.IStreamProcessor; 29 | import com.github.segator.proxylive.service.TokenService; 30 | import com.github.segator.proxylive.stream.*; 31 | import jakarta.annotation.PostConstruct; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | import org.springframework.beans.factory.annotation.Autowired; 35 | 36 | import java.io.IOException; 37 | import java.net.MalformedURLException; 38 | import java.net.URL; 39 | import java.util.Date; 40 | import java.util.Objects; 41 | 42 | /** 43 | * 44 | * @author Isaac Aymerich 45 | */ 46 | public class RemoteTranscodeTask implements IMultiplexerStreamer { 47 | private final Logger logger = LoggerFactory.getLogger(RemoteTranscodeTask.class); 48 | private final RemoteTranscoder transcoder; 49 | 50 | private final String channelID; 51 | private Date runDate; 52 | private VideoInputStream videoInputStream; 53 | private BroadcastCircularBufferedOutputStream multiplexerOutputStream; 54 | private boolean terminate = false; 55 | private boolean crashed = false; 56 | private int crashTimes = 0; 57 | private Long now; 58 | private byte[] buffer; 59 | 60 | @Autowired 61 | private TokenService tokenService; 62 | @Autowired 63 | private ProxyLiveConfiguration config; 64 | 65 | public RemoteTranscodeTask(String channelID,RemoteTranscoder transcoder) { 66 | this.transcoder = transcoder; 67 | this.channelID = channelID; 68 | } 69 | 70 | @PostConstruct 71 | public void initializeBean() throws Exception { 72 | buffer = new byte[config.getBuffers().getChunkSize()]; 73 | multiplexerOutputStream = new BroadcastCircularBufferedOutputStream(config.getBuffers().getBroadcastBufferSize()); 74 | } 75 | 76 | @Override 77 | public void initializate() throws MalformedURLException, IOException { 78 | 79 | } 80 | 81 | @Override 82 | public void terminate() { 83 | terminate = true; 84 | } 85 | 86 | public boolean isTerminated() { 87 | return terminate; 88 | } 89 | 90 | @Override 91 | public BroadcastCircularBufferedOutputStream getMultiplexer() { 92 | return multiplexerOutputStream; 93 | } 94 | 95 | @Override 96 | public String getSource() { 97 | return String.format("%s/view/%s/%s",transcoder.getEndpoint(), transcoder.getProfile(),channelID); 98 | } 99 | public String getAuthenticatedURL(){ 100 | String token = tokenService.createServiceAccountRequestToken(String.format("RemoteTranscode-%s",getIdentifier())); 101 | return String.format("%s?token=%s",getSource(),token); 102 | } 103 | 104 | 105 | private String getStringIdentifier(String message){ 106 | return "[id:" + getIdentifier() +"] " + message; 107 | } 108 | @Override 109 | public void run() { 110 | runDate = new Date(); 111 | int len; 112 | try { 113 | now = new Date().getTime(); 114 | videoInputStream = new WebInputStream(new URL(getAuthenticatedURL()),config); 115 | 116 | logger.debug(getStringIdentifier("Get Stream")); 117 | if (videoInputStream.connect()) { 118 | long lastReaded = new Date().getTime(); 119 | while (!terminate) { 120 | len = videoInputStream.read(buffer); 121 | if (len > 0) { 122 | multiplexerOutputStream.write(buffer, 0, len); 123 | lastReaded = new Date().getTime(); 124 | }else if((new Date().getTime() - lastReaded) > config.getSource().getReconnectTimeout()*1000) { 125 | throw new Exception(String.format("no data received on %d seconds",config.getSource().getReconnectTimeout())); 126 | }else{ 127 | Thread.sleep(10); 128 | } 129 | } 130 | }else{ 131 | throw new Exception (getStringIdentifier("Impossible to connect")); 132 | } 133 | } catch (Exception ex) { 134 | logger.error(getStringIdentifier(ex.getMessage())); 135 | 136 | if (crashTimes > 2 || terminate) { 137 | crashed = true; 138 | terminate = true; 139 | } else { 140 | crashTimes++; 141 | closeWebStream(); 142 | run(); 143 | } 144 | } finally { 145 | closeWebStream(); 146 | try { 147 | multiplexerOutputStream.close(); 148 | } catch (Exception ex) { 149 | } 150 | 151 | } 152 | } 153 | 154 | 155 | private void closeWebStream() { 156 | try { 157 | videoInputStream.close(); 158 | }catch(Exception ex) { 159 | } 160 | } 161 | 162 | @Override 163 | public int hashCode() { 164 | int hash = 7; 165 | hash = 89 * hash + Objects.hashCode(getIdentifier()); 166 | return hash; 167 | } 168 | 169 | @Override 170 | public boolean equals(Object o) { 171 | if (this == o) return true; 172 | if (o == null || getClass() != o.getClass()) return false; 173 | RemoteTranscodeTask that = (RemoteTranscodeTask) o; 174 | return transcoder.equals(that.transcoder) && channelID.equals(that.channelID); 175 | } 176 | 177 | @Override 178 | public String getIdentifier() { 179 | return channelID+"_"+transcoder.getEndpoint()+"_"+transcoder.getProfile(); 180 | } 181 | 182 | @Override 183 | public String toString() { 184 | return getIdentifier(); 185 | } 186 | 187 | @Override 188 | public boolean isCrashed() { 189 | return crashed; 190 | } 191 | 192 | @Override 193 | public IStreamProcessor getSourceProcessor() { 194 | return null; 195 | } 196 | 197 | @Override 198 | public Date startTaskDate() { 199 | return runDate; 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/tasks/StreamProcessorsSession.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.tasks; 25 | 26 | import com.github.segator.proxylive.ProxyLiveUtils; 27 | import com.github.segator.proxylive.controller.StreamController; 28 | import com.github.segator.proxylive.entity.ClientInfo; 29 | import com.github.segator.proxylive.entity.GEOInfo; 30 | import com.github.segator.proxylive.processor.DirectHLSTranscoderStreamProcessor; 31 | import com.github.segator.proxylive.processor.IStreamProcessor; 32 | 33 | import java.net.InetAddress; 34 | import java.net.UnknownHostException; 35 | import java.util.*; 36 | import jakarta.servlet.http.HttpServletRequest; 37 | 38 | import com.github.segator.proxylive.service.GeoIPService; 39 | import com.maxmind.geoip2.DatabaseReader; 40 | import com.maxmind.geoip2.exception.AddressNotFoundException; 41 | import com.maxmind.geoip2.model.AnonymousIpResponse; 42 | import com.maxmind.geoip2.model.CityResponse; 43 | import org.slf4j.Logger; 44 | import org.slf4j.LoggerFactory; 45 | import org.springframework.beans.factory.annotation.Autowired; 46 | import org.springframework.stereotype.Service; 47 | import org.springframework.util.MultiValueMap; 48 | import org.springframework.web.util.UriComponentsBuilder; 49 | 50 | /** 51 | * 52 | * @author Isaac Aymerich 53 | */ 54 | @Service 55 | public class StreamProcessorsSession { 56 | 57 | private final Logger logger = LoggerFactory.getLogger(StreamProcessorsSession.class); 58 | @Autowired 59 | private GeoIPService geoIPService; 60 | 61 | private final List clientInfoList; 62 | 63 | 64 | public StreamProcessorsSession() { 65 | clientInfoList = new ArrayList(); 66 | } 67 | 68 | public synchronized ClientInfo addClientInfo(ClientInfo client) { 69 | if (!clientInfoList.contains(client)) { 70 | clientInfoList.add(client); 71 | } 72 | return clientInfoList.get(clientInfoList.indexOf(client)); 73 | } 74 | 75 | public synchronized void removeClientInfo(ClientInfo client) { 76 | if (clientInfoList.contains(client)) { 77 | clientInfoList.remove(client); 78 | } 79 | } 80 | 81 | public synchronized List getClientInfoList() { 82 | return clientInfoList; 83 | } 84 | 85 | public synchronized ClientInfo manage(IStreamProcessor iStreamProcessor, HttpServletRequest request,String clientUser) throws UnknownHostException { 86 | ClientInfo client = new ClientInfo(); 87 | request.getQueryString(); 88 | 89 | if (clientUser == null || clientUser.trim().equals("null")) { 90 | clientUser = "guest"; 91 | } 92 | client.setClientUser(clientUser); 93 | client.setIp(InetAddress.getByName(ProxyLiveUtils.getRequestIP(request))); 94 | client.setBrowserInfo(ProxyLiveUtils.getBrowserInfo(request)); 95 | client = addClientInfo(client); 96 | if (geoIPService.isServiceEnabled()) { 97 | try { 98 | DatabaseReader geoDBReader = geoIPService.geoGEOInfoReader(); 99 | CityResponse cityResponse = geoDBReader.city(client.getIp()); 100 | 101 | if (cityResponse.getLocation() != null) { 102 | GEOInfo geoInfo = new GEOInfo(); 103 | geoInfo.setCity(cityResponse.getCity()); 104 | geoInfo.setCountry(cityResponse.getCountry()); 105 | geoInfo.setLocation(cityResponse.getLocation()); 106 | client.setGeoInfo(geoInfo); 107 | } 108 | }catch(AddressNotFoundException anfe){ 109 | }catch(Exception ex ){ 110 | logger.error("Error parsing user geodata", ex); 111 | } 112 | } 113 | if (!client.getStreams().contains(iStreamProcessor)) { 114 | client.getStreams().add(iStreamProcessor); 115 | } 116 | return client; 117 | } 118 | 119 | public synchronized void removeClientInfo(ClientInfo client, IStreamProcessor iStreamProcessor) { 120 | client.getStreams().remove(iStreamProcessor); 121 | if (client.getStreams().isEmpty()) { 122 | removeClientInfo(client); 123 | } 124 | } 125 | 126 | public synchronized DirectHLSTranscoderStreamProcessor getHLSStream(String clientIdentifier, String channel, String profile) { 127 | for (ClientInfo clientInfo : clientInfoList) { 128 | if (clientIdentifier.equals(clientInfo.getIp())) { 129 | for (IStreamProcessor streamProcessor : clientInfo.getStreams()) { 130 | profile=profile.equals("raw")?"":"_" +profile; 131 | if (streamProcessor.getTask().getIdentifier().equals(channel + profile + "_HLS")) { 132 | return (DirectHLSTranscoderStreamProcessor) streamProcessor; 133 | } 134 | } 135 | } 136 | } 137 | return null; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/github/segator/proxylive/tasks/StreamTaskFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2017 Isaac Aymerich . 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.segator.proxylive.tasks; 25 | 26 | import com.github.segator.proxylive.config.RemoteTranscoder; 27 | import com.github.segator.proxylive.entity.Channel; 28 | import com.github.segator.proxylive.processor.IStreamMultiplexerProcessor; 29 | import java.io.IOException; 30 | import org.springframework.context.annotation.Bean; 31 | import org.springframework.context.annotation.Configuration; 32 | import org.springframework.context.annotation.Scope; 33 | 34 | /** 35 | * 36 | * @author Isaac Aymerich 37 | */ 38 | @Configuration 39 | public class StreamTaskFactory { 40 | 41 | @Bean 42 | @Scope(value = "prototype") 43 | public HttpDownloaderTask HttpDownloaderTask(Channel channel) throws IOException { 44 | return new HttpDownloaderTask(channel); 45 | } 46 | 47 | @Bean 48 | @Scope(value = "prototype") 49 | public DirectTranscodeTask DirectTranscodeTask(Channel channel, String profile) throws IOException { 50 | return new DirectTranscodeTask(channel,profile); 51 | } 52 | 53 | @Bean 54 | @Scope(value = "prototype") 55 | public RemoteTranscodeTask RemoteTranscodeTask(String channelID, RemoteTranscoder transcoder) throws IOException { 56 | return new RemoteTranscodeTask(channelID,transcoder); 57 | } 58 | 59 | @Bean 60 | @Scope(value = "prototype") 61 | public HLSDirectTask HLSDirectTask(Channel channel, String profile) throws IOException { 62 | return new HLSDirectTask(channel,profile); 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.jackson.serialization.INDENT_OUTPUT: true 2 | management: 3 | server: 4 | port: 8090 5 | endpoints: 6 | prometheus: 7 | enabled: true 8 | metrics: 9 | enabled: true 10 | web: 11 | exposure: 12 | include: "*" 13 | metrics: 14 | export: 15 | prometheus: 16 | enabled: true 17 | geoIP: 18 | enabled: false 19 | url: https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz 20 | source: 21 | epg: 22 | refresh: 600 23 | channels: 24 | refresh: 60 25 | reconnectTimeout: 15 26 | userAgent: proxyLive 27 | streamTimeout: 30 28 | ffmpeg: 29 | path: ffmpeg 30 | profiles: 31 | - 32 | alias: "aac" 33 | parameters: "-i {input} {channelParameters} -sn -ac 2 -c:a libfdk_aac -b:a 320k -c:v copy" 34 | - 35 | alias: "360p" 36 | parameters: "-i {input} {channelParameters} -sn -c:a:0 libfdk_aac -ac 2 -b:a 96k -c:v libx264 -tune zerolatency -g 10 -vprofile high -level 4.0 -crf 18 -movflags +faststart -bufsize 15000k -maxrate 700k -preset veryslow -vf scale=-1:360,yadif=0" 37 | - 38 | alias: "480p" 39 | parameters: "-i {input} {channelParameters} -sn -c:a:0 libfdk_aac -ac 2 -b:a 196k -c:v libx264 -tune zerolatency -g 10 -vprofile high -level 4.0 -crf 18 -movflags +faststart -bufsize 15000k -maxrate 1500k -preset slow -vf scale=-1:484,yadif=0" 40 | - 41 | alias: "720p" 42 | parameters: "-i {input} {channelParameters} -sn -c:a:0 libfdk_aac -ac 2 -b:a 320k -c:v libx264 -tune zerolatency -g 10 -vprofile high -level 4.0 -crf 18 -movflags +faststart -bufsize 15000k -maxrate 3000k -preset fast -vf scale=-1:720,yadif=0" 43 | - 44 | alias: "1080p" 45 | parameters: "-i {input} {channelParameters} -sn -c:a:0 libfdk_aac -ac 2 -b:a 320k -c:v libx264 -tune zerolatency -g 300 -vprofile high -level 4.0 -movflags +faststart -bufsize 15000k -maxrate 5000k -preset faster -vf yadif=0" 46 | - 47 | alias: "vaapi1080p" 48 | parameters: "-hwaccel vaapi -vaapi_device /dev/dri/renderD128 -hwaccel_output_format vaapi -i {input} {channelParameters} -sn -c:a:0 aac -ac 2 -b:a 320k -c:v h264_vaapi -tune zerolatency -g 300 -level 41 -movflags +faststart -bufsize 15000k -maxrate 5000k -preset faster -vf format=vaapi,hwupload,deinterlace_vaapi" 49 | mpegTS: 50 | parameters: "-threads 0 -f mpegts -mpegts_copyts 1 -mpegts_flags +resend_headers -nostats -hide_banner" 51 | hls: 52 | tempPath: "/tmp" 53 | parameters: "-flags -global_header -avoid_negative_ts disabled -map_metadata -1 -start_at_zero -copyts -flags -global_header -vsync cfr -y -nostats -hide_banner -f hls -hls_time 2 -hls_list_size 10 -hls_flags delete_segments -hls_flags +append_list -hls_flags +discont_start -hls_flags +delete_segments" 54 | timeout: 30 55 | 56 | buffers: 57 | chunkSize: 131072 58 | broadcastBufferSize: 52428800 59 | 60 | authentication: 61 | expireInHours: 48 -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ( ( ) ) ) ( ( 2 | )\ ) )\ ) ( /( ( /( ( /( )\ ) )\ ) 3 | (()/( (()/( )\()) )\()) )\()) (()/( (()/( ( ( ( 4 | ___ ___ /(_)) /(_)) ((_)\ ((_)\ ((_)\ /(_)) /(_)) )\ )\ )\ ___ ___ 5 | |___| |___| (_)) (_)) ((_) __((_) __ ((_) (_)) (_)) ((_)((_) ((_) |___| |___| 6 | | _ \ | _ \ / _ \ \ \/ / \ \ / / | | |_ _| \ \ / / | __| 7 | | _/ | / | (_) | > < \ V / | |__ | | \ V / | _| 8 | |_| |_|_\ \___/ /_/\_\ |_| |____| |___| \_/ |___| 9 | 10 | ( ) ( ( ( 11 | ( )\ ) ( /( ( )\ ) ( ( )\ ) * ) )\ ) 12 | ( )\ (()/( )\()) )\ (()/( )\ )\ (()/( ` ) /( ( (()/( 13 | )((_) /(_)) ((_)\ ((((_)( /(_)) (((_) ((((_)( /(_)) ( )(_)) )\ /(_)) 14 | ((_)_ (_)) ((_) )\ _ )\ (_))_ )\___ )\ _ )\ (_)) (_(_()) ((_) (_)) 15 | | _ ) | _ \ / _ \ (_)_\(_) | \ ((/ __| (_)_\(_) / __| |_ _| | __| | _ \ 16 | | _ \ | / | (_) | / _ \ | |) | | (__ / _ \ \__ \ | | | _| | / 17 | |___/ |_|_\ \___/ /_/ \_\ |___/ \___| /_/ \_\ |___/ |_| |___| |_|_\ 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | %green(%d{"yyyy-MM-dd'T'HH:mm:ss,SSS"}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1}): %msg%n%throwable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------