001/******************************************************************************* 002 * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved. 003 * 004 * This file is part of AVPKit. 005 * 006 * AVPKit is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Lesser General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * AVPKit is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with AVPKit. If not, see <http://www.gnu.org/licenses/>. 018 *******************************************************************************/ 019 020package com.avpkit.core.io; 021 022import java.util.concurrent.ConcurrentHashMap; 023import java.util.concurrent.ConcurrentMap; 024 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028 029import com.avpkit.core.io.FfmpegIO; 030import com.avpkit.core.io.FileProtocolHandlerFactory; 031import com.avpkit.core.io.IURLProtocolHandler; 032import com.avpkit.core.io.IURLProtocolHandlerFactory; 033import com.avpkit.core.io.NullProtocolHandlerFactory; 034import com.avpkit.core.io.URLProtocolManager; 035 036/** 037 * This class manages URL protocols that we have handlers for. It is used 038 * (and called from) the Native code that integrates with FFMPEG. 039 * 040 * To register a handler you would use the singleton manager as follows: 041 * <code> 042 * <pre> 043 * URLProtocolManager mgr = URLProtocolManager.getManager(); 044 * mgr.registerFactory("myprotocol", myProtocolHandlerFactory); 045 * </pre> 046 * </code> 047 */ 048public class URLProtocolManager 049{ 050 private final Logger log = LoggerFactory.getLogger(this.getClass()); 051 private final ConcurrentMap<String, IURLProtocolHandlerFactory> mProtocols; 052 053 public static final String DEFAULT_PROTOCOL = "avpkitfile"; 054 public static final String NULL_PROTOCOL = "avpkitnull"; 055 056 private static final URLProtocolManager sManager = new URLProtocolManager(); 057 058 /** 059 * Factory method to get the global protocol manager. 060 */ 061 public static URLProtocolManager getManager() 062 { 063 return sManager; 064 } 065 066 /** 067 * Method to be called to initialize this library. 068 */ 069 public static void init() 070 { 071 // just calling this method causes sManager to be initialized 072 // in the static constructor, which is the only initialization 073 // we currently need. 074 } 075 076 // Don't let people create one of these; instead they must 077 // use the Singleton factory method. 078 private URLProtocolManager() 079 { 080 mProtocols = new ConcurrentHashMap<String, IURLProtocolHandlerFactory>(); 081 // Always register the DEFAULT 082 registerFactory(DEFAULT_PROTOCOL, new FileProtocolHandlerFactory()); 083 // And the NULL protocols 084 registerFactory(NULL_PROTOCOL, new NullProtocolHandlerFactory()); 085 } 086 087 /** 088 * Register a new factory for IURLProtocolHandlers for a given protocol. 089 * <p> 090 * FFMPEG is very picky; protocols must be only alpha characters (no numbers). 091 * </p> 092 * @param protocol The protocol, without colon, this factory is to be used for. 093 * @param factory The factory for the manager to use whenever a handler is requested for a registered protocol; 094 * null means disable handling for this factory. 095 * @return The factory that was previously used for that protocol (or null if none). 096 */ 097 public IURLProtocolHandlerFactory registerFactory(String protocol, 098 IURLProtocolHandlerFactory factory) 099 { 100 if (protocol == null) 101 throw new IllegalArgumentException("protocol required"); 102 103 IURLProtocolHandlerFactory oldFactory; 104 if (factory == null) 105 oldFactory = mProtocols.remove(protocol); 106 else 107 oldFactory = mProtocols.put(protocol, factory); 108 109 log.trace("Registering factory for URLProtocol: {}", protocol); 110 111 if (oldFactory == null) 112 { 113 // we previously didn't have something registered for this factory 114 // so tell FFMPEG we're now the protocol manager for this protocol. 115 log.trace("Letting FFMPEG know about an additional protocol: {}", 116 protocol); 117 FfmpegIO.registerProtocolHandler(protocol, this); 118 } 119 return oldFactory; 120 } 121 122 public IURLProtocolHandlerFactory getHandlerFactory(String protocol) 123 { 124 IURLProtocolHandlerFactory result = null; 125 126 log.trace("looking for protocol handler for: {}", protocol); 127 if (protocol == null || protocol.length() == 0) 128 throw new IllegalArgumentException("expected valid protocol"); 129 130 IURLProtocolHandlerFactory factory = mProtocols.get(protocol); 131 if (factory != null) 132 { 133 result = factory; 134 } 135 else 136 { 137 log.error("asked to get handler for unsupported protocol: {}", 138 protocol); 139 } 140 141 return result; 142 } 143 144 /* 145 * Get a IURLProtocolHandler for this url. 146 * <p> 147 * IMPORTANT: This function is called from native code, and so the name and signature cannot 148 * change without changing the native code. 149 * </p><p> 150 * This function is eventually invoked whenever someone tries to call url_open("yourprotocol:...", flags) 151 * from FFMPEG native code. It returns a protocol handler which will then have open(...) called on it. 152 * </p> 153 * @param url The URL we want to handle. 154 * @param flags Any flags that the url_open() function will want to pass. 155 * 156 * @returns A protocol handler to use. 157 */ 158 public IURLProtocolHandler getHandler(String url, int flags) 159 { 160 IURLProtocolHandler result = null; 161 162 log.trace("looking for protocol handler for: {}", url); 163 if (url == null || url.length() == 0) 164 throw new IllegalArgumentException("expected valid URL"); 165 int colonIndex = url.indexOf(":"); 166 String protocol = null; 167 if (colonIndex > 0) 168 { 169 protocol = url.substring(0, colonIndex); 170 } 171 else 172 { 173 protocol = DEFAULT_PROTOCOL; 174 } 175 176 IURLProtocolHandlerFactory factory = mProtocols.get(protocol); 177 if (factory != null) 178 { 179 result = factory.getHandler(url, flags); 180 } 181 else 182 { 183 log.error("asked to get handler for unsupported protocol: {}", 184 protocol); 185 } 186 187 return result; 188 } 189 190 /** 191 * Get the resource portion of a url. For example for the URL 192 * <pre> 193 * http://www.avpkit.com/core 194 * </pre> 195 * The protocol string is <code>http</code> and the resource 196 * string is <code>www.avpkit.com/core</code> 197 * 198 * @param url The url to parse 199 * @return The resource 200 */ 201 202 public static String getResourceFromURL(String url) 203 { 204 String retval = url; 205 if (url != null && url.length() > 0) 206 { 207 int colonIndex; 208 colonIndex = url.indexOf("://"); 209 if (colonIndex > 0) 210 retval = url.substring(colonIndex+3); 211 else { 212 colonIndex = url.indexOf(":"); 213 if (colonIndex > 0) 214 { 215 // remove the URL prefix 216 retval = url.substring(colonIndex + 1); 217 } 218 } 219 } 220 return retval; 221 } 222 223 /** 224 * Get the protocol portion of a url. For example for the URL 225 * <pre> 226 * http://www.avpkit.com/core 227 * </pre> 228 * The protocol string is <code>http</code> and the resource 229 * string is <code>//www.avpkit.com/core</code> 230 * 231 * @param url The url to parse 232 * @return The protocol 233 */ 234 235 public static String getProtocolFromURL(String url) 236 { 237 String retval = null; 238 if (url != null && url.length() > 0) 239 { 240 int colonIndex = url.indexOf(":"); 241 if (colonIndex > 0) 242 { 243 // remove the URL suffix 244 retval = url.substring(0, colonIndex); 245 } 246 } 247 return retval; 248 } 249 250}