mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 16:51:57 +00:00
embedded streams fix
This commit is contained in:
parent
3c35b99759
commit
c01528b309
8 changed files with 375 additions and 274 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
defaults.url=https://sentry.io/
|
defaults.url=https://sentry.io/
|
||||||
defaults.org=tapframe
|
defaults.org=tapframe
|
||||||
defaults.project=react-native
|
defaults.project=react-native
|
||||||
auth.token=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
|
# Using SENTRY_AUTH_TOKEN environment variable
|
||||||
|
|
@ -477,7 +477,7 @@
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||||
PRODUCT_NAME = Nuvio;
|
PRODUCT_NAME = "Nuvio";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
@ -508,8 +508,8 @@
|
||||||
"-lc++",
|
"-lc++",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub;
|
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app";
|
||||||
PRODUCT_NAME = Nuvio;
|
PRODUCT_NAME = "Nuvio";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,10 @@
|
||||||
<string>_googlecast._tcp</string>
|
<string>_googlecast._tcp</string>
|
||||||
<string>_CC1AD845._googlecast._tcp</string>
|
<string>_CC1AD845._googlecast._tcp</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>This app does not require microphone access.</string>
|
||||||
<key>RCTNewArchEnabled</key>
|
<key>RCTNewArchEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>RCTRootViewBackgroundColor</key>
|
<key>RCTRootViewBackgroundColor</key>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,7 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
<array/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
defaults.url=https://sentry.io/
|
defaults.url=https://sentry.io/
|
||||||
defaults.org=tapframe
|
defaults.org=tapframe
|
||||||
defaults.project=react-native
|
defaults.project=react-native
|
||||||
auth.token=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
|
# Using SENTRY_AUTH_TOKEN environment variable
|
||||||
|
|
@ -1064,10 +1064,8 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
const groupedAddonEpisodes: GroupedEpisodes = {};
|
const groupedAddonEpisodes: GroupedEpisodes = {};
|
||||||
|
|
||||||
addonVideos.forEach((video: any) => {
|
addonVideos.forEach((video: any) => {
|
||||||
const seasonNumber = video.season;
|
// Use season 0 for videos without season numbers (PPV-style content, specials, etc.)
|
||||||
if (!seasonNumber || seasonNumber < 1) {
|
const seasonNumber = video.season || 0;
|
||||||
return; // Skip season 0, which often contains extras
|
|
||||||
}
|
|
||||||
const episodeNumber = video.episode || video.number || 1;
|
const episodeNumber = video.episode || video.number || 1;
|
||||||
|
|
||||||
if (!groupedAddonEpisodes[seasonNumber]) {
|
if (!groupedAddonEpisodes[seasonNumber]) {
|
||||||
|
|
@ -1318,6 +1316,60 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
setError(null);
|
setError(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extract embedded streams from metadata videos (used by PPV-style addons)
|
||||||
|
const extractEmbeddedStreams = useCallback(() => {
|
||||||
|
if (!metadata?.videos) return;
|
||||||
|
|
||||||
|
// Check if any video has embedded streams
|
||||||
|
const videosWithStreams = (metadata.videos as any[]).filter(
|
||||||
|
(video: any) => video.streams && Array.isArray(video.streams) && video.streams.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (videosWithStreams.length === 0) return;
|
||||||
|
|
||||||
|
// Get the addon info from metadata if available
|
||||||
|
const addonId = (metadata as any).addonId || 'embedded';
|
||||||
|
const addonName = (metadata as any).addonName || metadata.name || 'Embedded Streams';
|
||||||
|
|
||||||
|
// Extract all streams from videos
|
||||||
|
const embeddedStreams: Stream[] = [];
|
||||||
|
for (const video of videosWithStreams) {
|
||||||
|
for (const stream of video.streams) {
|
||||||
|
embeddedStreams.push({
|
||||||
|
...stream,
|
||||||
|
name: stream.name || stream.title || video.title,
|
||||||
|
title: stream.title || video.title,
|
||||||
|
addonId,
|
||||||
|
addonName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embeddedStreams.length > 0) {
|
||||||
|
if (__DEV__) console.log(`✅ [extractEmbeddedStreams] Found ${embeddedStreams.length} embedded streams from ${addonName}`);
|
||||||
|
|
||||||
|
// Add to grouped streams
|
||||||
|
setGroupedStreams(prevStreams => ({
|
||||||
|
...prevStreams,
|
||||||
|
[addonId]: {
|
||||||
|
addonName,
|
||||||
|
streams: embeddedStreams,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Track addon response order
|
||||||
|
setAddonResponseOrder(prevOrder => {
|
||||||
|
if (!prevOrder.includes(addonId)) {
|
||||||
|
return [...prevOrder, addonId];
|
||||||
|
}
|
||||||
|
return prevOrder;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark loading as complete since we have streams
|
||||||
|
setLoadingStreams(false);
|
||||||
|
}
|
||||||
|
}, [metadata]);
|
||||||
|
|
||||||
const loadStreams = async () => {
|
const loadStreams = async () => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
try {
|
try {
|
||||||
|
|
@ -1478,6 +1530,9 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
if (__DEV__) console.log('🎬 [loadStreams] Using ID for Stremio addons:', stremioId);
|
if (__DEV__) console.log('🎬 [loadStreams] Using ID for Stremio addons:', stremioId);
|
||||||
processStremioSource(type, stremioId, false);
|
processStremioSource(type, stremioId, false);
|
||||||
|
|
||||||
|
// Also extract any embedded streams from metadata (PPV-style addons)
|
||||||
|
extractEmbeddedStreams();
|
||||||
|
|
||||||
// Monitor scraper completion status instead of using fixed timeout
|
// Monitor scraper completion status instead of using fixed timeout
|
||||||
const checkScrapersCompletion = () => {
|
const checkScrapersCompletion = () => {
|
||||||
setScraperStatuses(currentStatuses => {
|
setScraperStatuses(currentStatuses => {
|
||||||
|
|
@ -1814,8 +1869,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
||||||
if (metadata && metadata.videos && metadata.videos.length > 0) {
|
if (metadata && metadata.videos && metadata.videos.length > 0) {
|
||||||
logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`);
|
logger.log(`🎬 Metadata updated with ${metadata.videos.length} episodes, reloading series data`);
|
||||||
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
|
loadSeriesData().catch((error) => { if (__DEV__) console.error(error); });
|
||||||
|
// Also extract embedded streams from metadata videos (PPV-style addons)
|
||||||
|
extractEmbeddedStreams();
|
||||||
}
|
}
|
||||||
}, [metadata?.videos, type]);
|
}, [metadata?.videos, type, extractEmbeddedStreams]);
|
||||||
|
|
||||||
const loadRecommendations = useCallback(async () => {
|
const loadRecommendations = useCallback(async () => {
|
||||||
if (!settings.enrichMetadataWithTMDB) {
|
if (!settings.enrichMetadataWithTMDB) {
|
||||||
|
|
|
||||||
|
|
@ -410,9 +410,9 @@ export const StreamsScreen = () => {
|
||||||
isLoadingStreamsRef.current = true;
|
isLoadingStreamsRef.current = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check for Stremio addons
|
// Check for Stremio addons that support this content type (including embedded streams)
|
||||||
const hasStremioProviders = await stremioService.hasStreamProviders();
|
const hasStremioProviders = await stremioService.hasStreamProviders(type);
|
||||||
if (__DEV__) console.log('[StreamsScreen] hasStremioProviders:', hasStremioProviders);
|
if (__DEV__) console.log('[StreamsScreen] hasStremioProviders:', hasStremioProviders, 'for type:', type);
|
||||||
|
|
||||||
// Check for local scrapers (only if enabled in settings)
|
// Check for local scrapers (only if enabled in settings)
|
||||||
const hasLocalScrapers = settings.enableLocalScrapers && await localScraperService.hasScrapers();
|
const hasLocalScrapers = settings.enableLocalScrapers && await localScraperService.hasScrapers();
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,8 @@ export interface MetaDetails extends Meta {
|
||||||
released: string;
|
released: string;
|
||||||
season?: number;
|
season?: number;
|
||||||
episode?: number;
|
episode?: number;
|
||||||
|
thumbnail?: string;
|
||||||
|
streams?: Stream[]; // Embedded streams (used by PPV-style addons)
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -751,6 +753,7 @@ class StremioService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (__DEV__) console.log(`🔍 [getCatalog] Manifest URL for ${manifest.name}: ${manifest.url}`);
|
||||||
const { baseUrl, queryParams } = this.getAddonBaseURL(manifest.url);
|
const { baseUrl, queryParams } = this.getAddonBaseURL(manifest.url);
|
||||||
// Candidate 1: Path-style skip URL: /catalog/{type}/{id}/skip={N}.json
|
// Candidate 1: Path-style skip URL: /catalog/{type}/{id}/skip={N}.json
|
||||||
const urlPathStyle = `${baseUrl}/catalog/${type}/${encodedId}/skip=${pageSkip}.json${queryParams ? `?${queryParams}` : ''}`;
|
const urlPathStyle = `${baseUrl}/catalog/${type}/${encodedId}/skip=${pageSkip}.json${queryParams ? `?${queryParams}` : ''}`;
|
||||||
|
|
@ -762,15 +765,40 @@ class StremioService {
|
||||||
if (queryParams) urlQueryStyle += `&${queryParams}`;
|
if (queryParams) urlQueryStyle += `&${queryParams}`;
|
||||||
urlQueryStyle += filterQuery;
|
urlQueryStyle += filterQuery;
|
||||||
|
|
||||||
// Try path-style first, then fallback to query-style
|
// For page 1, also try simple URL without skip (some addons don't support skip)
|
||||||
|
const urlSimple = `${baseUrl}/catalog/${type}/${encodedId}.json${queryParams ? `?${queryParams}` : ''}`;
|
||||||
|
const urlSimpleWithFilters = urlSimple + (urlSimple.includes('?') ? filterQuery : (filterQuery ? `?${filterQuery.slice(1)}` : ''));
|
||||||
|
|
||||||
|
// Try URLs in order of compatibility: simple (page 1 only), path-style, query-style
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await this.retryRequest(async () => axios.get(urlPathWithFilters));
|
// For page 1, try simple URL first (best compatibility)
|
||||||
|
if (pageSkip === 0) {
|
||||||
|
if (__DEV__) console.log(`🔍 [getCatalog] Trying simple URL for ${manifest.name}: ${urlSimpleWithFilters}`);
|
||||||
|
response = await this.retryRequest(async () => axios.get(urlSimpleWithFilters));
|
||||||
|
// Check if we got valid metas - if empty, try other styles
|
||||||
|
if (!response?.data?.metas || response.data.metas.length === 0) {
|
||||||
|
throw new Error('Empty response from simple URL');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Not page 1, skip to path-style');
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
response = await this.retryRequest(async () => axios.get(urlQueryStyle));
|
if (__DEV__) console.log(`🔍 [getCatalog] Trying path-style URL for ${manifest.name}: ${urlPathWithFilters}`);
|
||||||
|
response = await this.retryRequest(async () => axios.get(urlPathWithFilters));
|
||||||
|
// Check if we got valid metas - if empty, try query-style
|
||||||
|
if (!response?.data?.metas || response.data.metas.length === 0) {
|
||||||
|
throw new Error('Empty response from path-style URL');
|
||||||
|
}
|
||||||
} catch (e2) {
|
} catch (e2) {
|
||||||
throw e2;
|
try {
|
||||||
|
if (__DEV__) console.log(`🔍 [getCatalog] Trying query-style URL for ${manifest.name}: ${urlQueryStyle}`);
|
||||||
|
response = await this.retryRequest(async () => axios.get(urlQueryStyle));
|
||||||
|
} catch (e3) {
|
||||||
|
if (__DEV__) console.log(`❌ [getCatalog] All URL styles failed for ${manifest.name}`);
|
||||||
|
throw e3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1050,7 +1078,6 @@ class StremioService {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
|
|
||||||
const addons = this.getInstalledAddons();
|
const addons = this.getInstalledAddons();
|
||||||
logger.log('📌 [getStreams] Installed addons:', addons.map(a => ({ id: a.id, name: a.name, url: a.url })));
|
|
||||||
|
|
||||||
// Check if local scrapers are enabled and execute them first
|
// Check if local scrapers are enabled and execute them first
|
||||||
try {
|
try {
|
||||||
|
|
@ -1206,9 +1233,6 @@ class StremioService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the detailed resources structure for debugging
|
|
||||||
logger.log(`📋 [getStreams] Checking addon ${addon.id} resources:`, JSON.stringify(addon.resources));
|
|
||||||
|
|
||||||
let hasStreamResource = false;
|
let hasStreamResource = false;
|
||||||
let supportsIdPrefix = false;
|
let supportsIdPrefix = false;
|
||||||
|
|
||||||
|
|
@ -1225,11 +1249,9 @@ class StremioService {
|
||||||
// Check if this addon supports the ID prefix (generic: any prefix that matches start of id)
|
// Check if this addon supports the ID prefix (generic: any prefix that matches start of id)
|
||||||
if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
|
if (Array.isArray(typedResource.idPrefixes) && typedResource.idPrefixes.length > 0) {
|
||||||
supportsIdPrefix = typedResource.idPrefixes.some(p => id.startsWith(p));
|
supportsIdPrefix = typedResource.idPrefixes.some(p => id.startsWith(p));
|
||||||
logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${typedResource.idPrefixes.join(', ')} → matches=${supportsIdPrefix}`);
|
|
||||||
} else {
|
} else {
|
||||||
// If no idPrefixes specified, assume it supports all prefixes
|
// If no idPrefixes specified, assume it supports all prefixes
|
||||||
supportsIdPrefix = true;
|
supportsIdPrefix = true;
|
||||||
logger.log(`🔍 [getStreams] Addon ${addon.id} has no prefix restrictions, assuming support`);
|
|
||||||
}
|
}
|
||||||
break; // Found the stream resource object, no need to check further
|
break; // Found the stream resource object, no need to check further
|
||||||
}
|
}
|
||||||
|
|
@ -1241,11 +1263,9 @@ class StremioService {
|
||||||
// For simple string resources, check addon-level idPrefixes (generic)
|
// For simple string resources, check addon-level idPrefixes (generic)
|
||||||
if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
|
if (addon.idPrefixes && Array.isArray(addon.idPrefixes) && addon.idPrefixes.length > 0) {
|
||||||
supportsIdPrefix = addon.idPrefixes.some(p => id.startsWith(p));
|
supportsIdPrefix = addon.idPrefixes.some(p => id.startsWith(p));
|
||||||
logger.log(`🔍 [getStreams] Addon ${addon.id} supports prefixes: ${addon.idPrefixes.join(', ')} → matches=${supportsIdPrefix}`);
|
|
||||||
} else {
|
} else {
|
||||||
// If no idPrefixes specified, assume it supports all prefixes
|
// If no idPrefixes specified, assume it supports all prefixes
|
||||||
supportsIdPrefix = true;
|
supportsIdPrefix = true;
|
||||||
logger.log(`🔍 [getStreams] Addon ${addon.id} has no prefix restrictions, assuming support`);
|
|
||||||
}
|
}
|
||||||
break; // Found the simple stream resource string and type support
|
break; // Found the simple stream resource string and type support
|
||||||
}
|
}
|
||||||
|
|
@ -1254,18 +1274,10 @@ class StremioService {
|
||||||
|
|
||||||
const canHandleRequest = hasStreamResource && supportsIdPrefix;
|
const canHandleRequest = hasStreamResource && supportsIdPrefix;
|
||||||
|
|
||||||
if (!hasStreamResource) {
|
|
||||||
logger.log(`❌ [getStreams] Addon ${addon.id} does not support streaming ${type}`);
|
|
||||||
} else if (!supportsIdPrefix) {
|
|
||||||
logger.log(`❌ [getStreams] Addon ${addon.id} supports ${type} but its idPrefixes did not match id=${id}`);
|
|
||||||
} else {
|
|
||||||
logger.log(`✅ [getStreams] Addon ${addon.id} supports streaming ${type} for id=${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return canHandleRequest;
|
return canHandleRequest;
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log('📊 [getStreams] Stream capable addons:', streamAddons.map(a => a.id));
|
|
||||||
|
|
||||||
if (streamAddons.length === 0) {
|
if (streamAddons.length === 0) {
|
||||||
logger.warn('⚠️ [getStreams] No addons found that can provide streams');
|
logger.warn('⚠️ [getStreams] No addons found that can provide streams');
|
||||||
|
|
@ -1596,24 +1608,50 @@ class StremioService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any installed addons can provide streams
|
// Check if any installed addons can provide streams (including embedded streams in metadata)
|
||||||
async hasStreamProviders(): Promise<boolean> {
|
async hasStreamProviders(type?: string): Promise<boolean> {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
const addons = Array.from(this.installedAddons.values());
|
const addons = Array.from(this.installedAddons.values());
|
||||||
|
|
||||||
for (const addon of addons) {
|
for (const addon of addons) {
|
||||||
if (addon.resources && Array.isArray(addon.resources)) {
|
if (addon.resources && Array.isArray(addon.resources)) {
|
||||||
// Check for 'stream' resource in the modern format
|
// Check for explicit 'stream' resource
|
||||||
const hasStreamResource = addon.resources.some(resource =>
|
const hasStreamResource = addon.resources.some(resource =>
|
||||||
typeof resource === 'string'
|
typeof resource === 'string'
|
||||||
? resource === 'stream'
|
? resource === 'stream'
|
||||||
: resource.name === 'stream'
|
: (resource as any).name === 'stream'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasStreamResource) {
|
if (hasStreamResource) {
|
||||||
|
// If type specified, also check if addon supports this type
|
||||||
|
if (type) {
|
||||||
|
const supportsType = addon.types?.includes(type) ||
|
||||||
|
addon.resources.some(resource =>
|
||||||
|
typeof resource === 'object' &&
|
||||||
|
(resource as any).name === 'stream' &&
|
||||||
|
(resource as any).types?.includes(type)
|
||||||
|
);
|
||||||
|
if (supportsType) return true;
|
||||||
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also check for addons with meta resource that support the type
|
||||||
|
// These addons might provide embedded streams within metadata
|
||||||
|
if (type) {
|
||||||
|
const hasMetaResource = addon.resources.some(resource =>
|
||||||
|
typeof resource === 'string'
|
||||||
|
? resource === 'meta'
|
||||||
|
: (resource as any).name === 'meta'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasMetaResource && addon.types?.includes(type)) {
|
||||||
|
// This addon provides meta for the type - might have embedded streams
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue